View.getViewTreeObserver(). addOnGlobalLayoutListener утечки Фрагмент

Когда я использую GlobalLayoutListener, чтобы узнать, открыт ли экран softKeyboard или нет, фрагмент больше не будет уничтожен garbageCollected после его уничтожения.

Что я делаю:

  • Я удаляю Listener в onDestroy() моего фрагмента
  • Я установил Listener в null в onDestroy()
  • Я установил представление, которое наблюдается в null в onDestroy()

Все еще просачивается фрагмент.

Есть ли у кого-то подобная проблема и знает, как это исправить?

Мой 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

Я считаю, что сильно удалить Listener, на который ссылается объект View, в onDestroy() слишком поздно. Этот метод переопределения происходит после onDestroyView(), который должен "... очищать ресурсы, связанные с его представлением". Вместо этого вы можете использовать тот же код в onStop(). Хотя я не использовал эту технику.

Я могу предложить этот код, который я использовал без каких-либо проблем с отладчиком.

// 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);
        }
    }
});

Примечания:

  • Так как getViewTreeObserver используется для макетов, обычно вам нужен этот прослушиватель только на короткое время. Следовательно, слушатель немедленно удаляется.
  • Второй вызов removeOnGlobalLayoutListener() должен быть вычеркнут Studio, поскольку он недоступен до JELLY_BEAN.
  • Код Pragma @SuppressWarnings("deprecation") не нужен, если вы используете Android Studio.
  • Код myView = rootView.findViewById(R.id.myView); может потребоваться изменить на более подходящий код для вашего приложения или ситуации.

Ответ 2

Хорошо, может быть, это немного переборщило, но трудно сказать что-то точно без источников, поэтому попробуйте это. Сделайте свой gLayoutListener статическим внутренним классом (чтобы не содержать ссылки на ваш фрагмент).

Если вам нужно выполнить некоторые операции с фрагментом или его поля внутри слушателя, создайте WeakReference<YourFragment> внутри конструктора вашего пользовательского класса прослушивателя и получите доступ к фрагменту через эту ссылку. Не забудьте сделать чек weakref.get() != null.

Ответ 3

Вместо этого вы можете попробовать создать пользовательский макет и поместить его в качестве корневого представления в свой XML файл.

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

}

Затем переопределите метод onsizechanged

@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 {

    }
}

Я полагаю, что ваше приложение поддерживает только один режим (портретный или альбомный)

Обновление

Сделайте все в

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

}

Потому что я думаю, что вы инициализируете слушателя в onCreateView(), поэтому слушатель должен быть удален в onDestoryView(). onDestroy() будет вызываться только тогда, когда фрагмент будет уничтожен, а не во время изменения состояния.

Проверьте жизненный цикл фрагмента

Ответ 4

У меня была такая же проблема, но я разрешил ее, удалив слушателя в onDestroy(). Обратите внимание на способ использования, измененный в 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();
    }

Ответ 5

У меня также была эта проблема в пользовательском представлении. Я зарегистрировал onPreDrawListener над дочерним представлением в конструкторе пользовательского вида и отменил его в onDetachedFromWindow. Утечка памяти сохранилась. Чтобы решить эту проблему, я все испробовал, но в конце концов мне пришлось написать альтернативный механизм, не основанный на TreeObserver.