Утечка памяти FragmentStatePagerAdapter (вложенные фрагменты с viewpager)

У меня есть "утечка памяти" в моем адаптере (кавычки будут объяснены позже). В настоящее время я использую вложенные фрагменты для размещения пейджера.

Моя установка выглядит следующим образом:
1. Активность (пустая активность, в которой размещается фрагмент A)
2. Фрагмент A — фрагмент, в котором размещается viewpager с Fragmentstatepageradapter. Каждая страница просмотра содержит фрагмент B.
3. Фрагмент B — фрагмент, содержащий изображение.

Все работает отлично, за исключением случаев, когда происходит изменение конфигурации. Наблюдая за кучей, кажется, что она увеличивается на 100 КБ каждый раз, когда происходит ротация. Вручную GCing не освобождает память.

Вещи, которые я пробовал:
1. Замените фрагмент B пустым фрагментом - возникает та же проблема, поэтому проблема не в изображении.
2. Удалите оба фрагмента A и B и поверните действие. Утечек памяти не происходит, так что это не действие.
3. Использовал MAT перед любыми изменениями ориентации и после поворота около 50 раз, чтобы получить кучу. MAT показывает 1 главного подозреваемого, который является моим классом адаптера. Он показывает 7 МБ сохраненной кучи (очень маленькая неглубокая куча) наблюдателей, например:

array java.util.ArrayList @ 0x42079938 24 7,000,832 
.\mObservers android.database.DataSetObservable @ 0x42053508 16 7,000,848 
..\mObservable com.example.main.Adapter@ 0x4205a048 40 7,001,416 

Почему я использую viewpager внутри фрагмента:
1. Я хочу сохранить состояние адаптера и других переменных, связанных с viewpager, установив setretaininstance(true).
2. После изменения конфигурации Я не создаю адаптер заново, а использую старый для подключения к странице просмотра.
3. Если я не использую старый адаптер повторно, а создаю новый адаптер после изменения конфигурации, утечка памяти исчезает.
4. утечка памяти также исчезает после того, как я закрою действие и вернусь к предыдущему действию.

Есть идеи? Был бы признателен за любую помощь.

Спасибо, Джей Си


person JC.    schedule 30.12.2013    source источник


Ответы (2)


У меня была аналогичная утечка памяти, которая теперь устранена.

В моем соответствующем фрагменте A я создавал экземпляр FragmentStatePagerAdapter с this.getFragmentManager() вместо this.getChildFragmentManager(), поскольку вложенные фрагменты уже существуют.

Пожалуйста, дайте мне знать, если это также решит вашу проблему.

person Carlos Castro    schedule 07.01.2014
comment
Спасибо чувак! Сам бы никогда не догадался (хоть это и логично). - person VipulKumar; 04.12.2014
comment
Должен быть помечен как правильный awnser. Спасибо! - person shalama; 08.02.2016
comment
подскажите, пожалуйста, куда поместить getChildFragmentManager()? Я делаю следующее: MainActivity: fragmentAdapter = new FragmentAdapter(getSupportFragmentManager()); и в адаптере фрагментов: public FragmentAdapter(FragmentManager fm) { super(fm); } - person Muhammad; 07.03.2016
comment
Пожалуйста, попробуйте следующее в вашей MainActivity: fragmentAdapter = new FragmentAdapter(getChildFragmentManager()); - person Carlos Castro; 07.03.2016
comment
Отличное исправление, большое спасибо. - person Mohsin; 14.06.2018
comment
чувак ты спас мне жизнь. я бы дал вам +2, если бы я мог. - person filthy_wizard; 20.01.2019
comment
поэтому я всегда должен использовать childfragmentmanager во вложенном фрагменте. не знал этого. - person filthy_wizard; 20.01.2019
comment
Спасибо вам! Спас день! - person Zhangali Bidaibekov; 09.04.2020

У меня была аналогичная проблема, я использовал ViewPager2, и мне нужно было использовать getChildFragmentManager() вместо getSupportFragmentManager(), потому что я хочу, чтобы PageFragment ссылался на родительский фрагмент (в котором находится ViewPager) с requireParentFragment()

Н.Б. Когда я использовал getSupportFragmentManager(), я получил java.lang.IllegalStateException Fragment PageFragment{f152edf} (af30cf2b-acf1-4930-9b83-03ac8144cfc6) f49} is not a child Fragment

Другая причина, по которой я использовал getChildFragmentManager(), заключается в том, что я использую компоненты навигации, поэтому мне не нужно управлять дочерними фрагментами ViewPager с помощью того же диспетчера фрагментов компонентов навигации, что и getSupportFragmentManager().

Вот лог LeakCanary

2020-09-15 05:39:33.461 9611-9689/.... D/LeakCanary: ┬───
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │ GC Root: Local variable in native code
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: ├─ android.os.HandlerThread instance
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    Leaking: NO (PathClassLoader↓ is not leaking)
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    Thread name: 'LeakCanary-Heap-Dump'
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    ↓ HandlerThread.contextClassLoader
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: ├─ dalvik.system.PathClassLoader instance
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    Leaking: NO (InternalLeakCanary↓ is not leaking and A ClassLoader is never leaking)
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    ↓ PathClassLoader.runtimeInternalObjects
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: ├─ java.lang.Object[] array
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    Leaking: NO (InternalLeakCanary↓ is not leaking)
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    ↓ Object[].[597]
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: ├─ leakcanary.internal.InternalLeakCanary class
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    ↓ static InternalLeakCanary.resumedActivity
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: ├─ .....ui.MainActivity instance
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    Leaking: NO (Activity#mDestroyed is false)
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    ↓ MainActivity.mLifecycleRegistry
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │                   ~~~~~~~~~~~~~~~~~~
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: ├─ androidx.lifecycle.LifecycleRegistry instance
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    Leaking: UNKNOWN
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    ↓ LifecycleRegistry.mObserverMap
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │                        ~~~~~~~~~~~~
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: ├─ androidx.arch.core.internal.FastSafeIterableMap instance
2020-09-15 05:39:33.462 9611-9689/.... D/LeakCanary: │    Leaking: UNKNOWN
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    ↓ FastSafeIterableMap.mHashMap
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │                          ~~~~~~~~
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: ├─ java.util.HashMap instance
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    Leaking: UNKNOWN
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    ↓ HashMap.table
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │              ~~~~~
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: ├─ java.util.HashMap$Node[] array
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    Leaking: UNKNOWN
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    ↓ HashMap$Node[].[5]
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │                     ~~~
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: ├─ java.util.HashMap$Node instance
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    Leaking: UNKNOWN
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    ↓ HashMap$Node.key
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │                   ~~~
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: ├─ androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3 instance
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    Leaking: UNKNOWN
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    Anonymous class implementing androidx.lifecycle.LifecycleEventObserver
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    ↓ FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3.this$1
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │                                                          ~~~~~~
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: ├─ androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer instance
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    Leaking: UNKNOWN
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │    ↓ FragmentStateAdapter$FragmentMaxLifecycleEnforcer.mViewPager
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: │                                                        ~~~~~~~~~~
2020-09-15 05:39:33.463 9611-9689/.... D/LeakCanary: ├─ androidx.viewpager2.widget.ViewPager2 instance
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: │    Leaking: YES (View detached and has parent)
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: │    mContext instance of .....ui.MainActivity with mDestroyed = false
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: │    View#mParent is set
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: │    View#mAttachInfo is null (view detached)
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: │    View.mID = R.id.viewpager
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: │    View.mWindowAttachCount = 1
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: │    ↓ ViewPager2.mParent
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: ├─ androidx.coordinatorlayout.widget.CoordinatorLayout instance
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: │    Leaking: YES (ViewPager2↑ is leaking and View detached and has parent)
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: │    mContext instance of .....ui.MainActivity with mDestroyed = false
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: │    View#mParent is set
2020-09-15 05:39:33.465 9611-9689/.... D/LeakCanary: │    View#mAttachInfo is null (view detached)
2020-09-15 05:39:33.466 9611-9689/.... D/LeakCanary: │    View.mWindowAttachCount = 1
2020-09-15 05:39:33.466 9611-9689/.... D/LeakCanary: │    ↓ CoordinatorLayout.mParent
2020-09-15 05:39:33.466 9611-9689/.... D/LeakCanary: ╰→ androidx.drawerlayout.widget.DrawerLayout instance
2020-09-15 05:39:33.466 9611-9689/.... D/LeakCanary: ​     Leaking: YES (ObjectWatcher was watching this because .....ui.fragments.ReadFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
2020-09-15 05:39:33.466 9611-9689/.... D/LeakCanary: ​     key = e3b96092-06d5-48ae-93bf-b38680cc0c35
2020-09-15 05:39:33.466 9611-9689/.... D/LeakCanary: ​     watchDurationMillis = 6413
2020-09-15 05:39:33.466 9611-9689/.... D/LeakCanary: ​     retainedDurationMillis = 1400
2020-09-15 05:39:33.466 9611-9689/.... D/LeakCanary: ​     mContext instance of .....ui.MainActivity with mDestroyed = false
2020-09-15 05:39:33.467 9611-9689/.... D/LeakCanary: ​     View#mParent is null
2020-09-15 05:39:33.467 9611-9689/.... D/LeakCanary: ​     View#mAttachInfo is null (view detached)
2020-09-15 05:39:33.467 9611-9689/.... D/LeakCanary: ​     View.mID = R.id.drawer_layout

Итак, чтобы решить эту проблему, я очистил все дочерние фрагменты, которые были вне ViewPager; для этого мне пришлось зарегистрировать все идентификаторы фрагментов и переопределить containsItem(), чтобы очистить их там, как показано ниже:

public class PageFragmentPagerAdapter extends FragmentStateAdapter {

    private FragmentManager mFragmentMgr;
    private List<Integer> currentPageIds = new ArrayList<>();

    public PageFragmentPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
        super(fragmentManager, lifecycle);
        mFragmentMgr = fragmentManager;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        PageFragment pageFragment = PageFragment.newInstance(position);
        currentPageIds.add(position);
        return pageFragment;
    }


    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public int getItemCount() {
        return N_PAGES;
    }

    @Override
    public boolean containsItem(long itemId) {
        for (Integer id : currentPageIds)
            if (id == itemId) {
                currentPageIds.remove(Integer.valueOf(String.valueOf(itemId)));
                clearFragment(id);
                break;
            }
        return super.containsItem(itemId);
    }

    private void clearFragment(int fragmentId) {
        FragmentTransaction transaction = mFragmentMgr.beginTransaction();
        PageFragment fragment = (PageFragment) mFragmentMgr.findFragmentByTag("f" + fragmentId);
        if (fragment != null) {
            transaction.remove(fragment);
        }
        transaction.commitAllowingStateLoss();
    }

}

Во-вторых, не создавайте экземпляр адаптера ViewPager с жизненным циклом активности с requireActivity().getLifecycle(), вместо этого используйте жизненный цикл его фрагмента getViewLifecycleOwner().getLifecycle(), как показано ниже.

PageFragmentPagerAdapter mPagerAdapter = new PageFragmentPagerAdapter(
     getChildFragmentManager(), getViewLifecycleOwner().getLifecycle());
person Zain    schedule 15.09.2020