view.getViewTreeObserver().addOnGlobalLayoutListen

2020-02-08 06:43发布

问题:

When I use the GlobalLayoutListener to see if the softKeyboard is opened or not the fragment is not garbageCollected anymore after it is destroyed.

What I do:

  • I remove the Listener in the onDestroy() of my Fragment
  • I set the Listener to null in onDestroy()
  • I set the view that is observed to null in onDestroy()

Still leaks the fragment.

Does anyone had a similar issue and knows a fix for it??

My onDestroy:

   @Override
public void onDestroy(){
    Log.d(TAG , "onDestroy");

    if(Build.VERSION.SDK_INT < 16){
        view.getViewTreeObserver().removeGlobalOnLayoutListener(gLayoutListener);
    }else{
        view.getViewTreeObserver().removeOnGlobalLayoutListener(gLayoutListener);
    }

    view = null;
    gLayoutListener = null;



    super.onDestroy();
    }

回答1:

I believe strongly removing the Listener, referenced by a View object, in onDestroy() is too late. This override method occurs after onDestroyView(), which supposed to "...clean up resources associated with its View." You may use the same code in onStop() instead. Although I did not use this technique.

I can suggest this code, which I have used without any issues with the debugger.

// Code below is an example. Please change it to code that is more applicable to your app.
final View myView = rootView.findViewById(R.id.myView);
myView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @SuppressLint("NewApi") @SuppressWarnings("deprecation")
    @Override
    public void onGlobalLayout() {

        // Obtain layout data from view...
        int w = myView.getWidth();
        int h = myView.getHeight();
        // ...etc.

        // Once data has been obtained, this listener is no longer needed, so remove it...
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            myView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
        else {
            myView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        }
    }
});

Notes:

  • Since getViewTreeObserver is used for layouts, normally you need this listener for a short time only. Hence the listener is removed immediately.
  • The second call to removeOnGlobalLayoutListener() should be crossed out by the Studio since it is not available before JELLY_BEAN.
  • Pragma code @SuppressWarnings("deprecation") is not necessary if you're using Android Studio.
  • Code myView = rootView.findViewById(R.id.myView); may need to change to a more applicable code to your app or situation.


回答2:

I had this same issue, but I resolved it by removing the listener in onDestroy(). Note the method to use changed around JellyBean.

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
          mView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
    }

    @Override
    public void onDestroy() {       
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
            mView.getViewTreeObserver().removeGlobalOnLayoutListener(mGlobalLayoutListener);
        } else {
            mView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
        }

        super.onDestroy();
    }


回答3:

Well, maybe it's a bit overkill, but it's hard to tell anything for sure without sources, so try this. Make your gLayoutListener a static inner class (to not keep a strong reference to your fragment).

If you need to perform some operations with fragment or it's fields inside listener, create a WeakReference<YourFragment> inside the constructor of your custom listener class and access your fragment through this reference. Don't forget to do the check weakref.get() != null.



回答4:

Instead of using this, you can try creating a Custom Layout and place this as your root view in your xml.

class CustomLayout extends LinearLayout{
      public CustomLayout(Context context, AttributeSet attrs) {
             super(context, attrs);       
      } 

}

Then override the onsizechanged method

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    if (h < oldh) {
        // there is a difference, means keyboard is open.
    } else {

    }
}

I am under the assumption that your application supports only one mode (Portrait or Landscape)

Update:

Do everything in

@Override
public void onDestroyView(){
     super.onDestroyView();

}

Because I think you are initializing listener in onCreateView(), so listener should be removed in onDestoryView(). onDestroy() will be called only when the fragment is destroyed, not during state change.

Check the Fragment life cycle



回答5:

I also had this problem in a custom view. I registered a onPreDrawListener over a child view in the custom view constructor, and de-registered it in onDetachedFromWindow. The memory leak persisted. To solve it I tried everything, but in the end I had to write an alternative mechanism not based on TreeObserver.