FragmentStatePagerAdapter 内存泄漏(嵌套带有 ViewPager 的片段)

14

我的适配器中存在“内存泄漏”问题(稍后将解释引号的含义)。我目前使用嵌套片段来托管视图页面。

我的设置如下:
1. 活动(空活动,托管 Fragment A)
2. Fragment A - 托管带有 Fragmentstatepageradapter 的视图页面的片段。每个视图页面都托管片段 B。
3. Fragment B - 包含 ImageView 的片段。

一切都很好,只是当发生配置更改时出现问题。监控堆,似乎每次旋转时都会增长 100 kb。手动进行垃圾回收不会释放内存。

我尝试过的事情:
1. 用空白片段替换 Fragment B - 发生相同的问题,因此不是 ImageView 导致的问题。
2. 删除 Fragment A 和 B 并旋转活动。不会出现内存泄露,因此不是活动的问题。
3. 在任何方向更改之前和旋转约 50 次后使用 MAT 来获取堆。MAT 显示一个主要的嫌疑犯,即我的适配器类。它显示了保留堆的 7MB(非常小的浅堆)的 observers,如下所示:

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 
为什么我在一个fragment中使用viewpager:
1. 我想通过设置setretaininstance(true)来保持与viewpager相关的适配器和其他变量的状态。
2. 在配置更改后,我不重新创建适配器,而是使用旧的适配器附加到viewpager。
3. 如果我不重用旧适配器,而是在配置更改后创建新适配器,则会消除内存泄漏问题。
4. 当我关闭活动并返回前一个活动后,内存泄漏问题也会消失。

有任何想法吗? 感谢任何帮助。

谢谢, JC

5个回答

38

我之前有一个类似的内存泄漏问题,现在已经解决了。

在我的对应 Fragment A 中,我使用 FragmentStatePagerAdapter 实例化时,使用了 this.getFragmentManager() 而不是 this.getChildFragmentManager(),因为有嵌套的 Fragment。

如果这个方法也能解决你的问题,请告诉我。


1
应该标记为正确答案。谢谢! - shalama
1
请问您应该把getChildFragmentManager()放在哪里? 我正在进行以下操作: MainActivity: fragmentAdapter = new FragmentAdapter(getSupportFragmentManager());而在Fragment Adapter中: public FragmentAdapter(FragmentManager fm) { super(fm); } - Muhammad
1
请在您的MainActivity中尝试以下操作:fragmentAdapter = new FragmentAdapter(getChildFragmentManager()); - Carlos Castro
兄弟,你救了我的命。如果可以的话,我会给你+2的评价。 - filthy_wizard
所以我应该在嵌套的片段中始终使用ChildFragmentManager。我不知道这一点。 - filthy_wizard
显示剩余3条评论

4

我遇到了类似的问题,我使用了 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 的子片段,而应该使用自己的管理器。

这是 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 的子片段;为此,我必须注册所有片段 ID,并重写 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();
    }

}

第二件事,不要使用requireActivity().getLifecycle()来实例化ViewPager适配器的生命周期,而是像下面这样使用其片段的生命周期getViewLifecycleOwner().getLifecycle()

PageFragmentPagerAdapter mPagerAdapter = new PageFragmentPagerAdapter(
     getChildFragmentManager(), getViewLifecycleOwner().getLifecycle());

0

我遇到了同样的问题。我将TabLayoutMediator的值设置为null,但是泄漏并没有在此之后消失,我在onDestroyView中分离了TabLayoutMediator,这对我起作用。

override fun onDestroyView() {
    super.onDestroyView()

    mediator?.detach()
    mediator = null
    _binding = null
}

0
任何人有疑问,这个在ViewPager2(androidx)中泄漏的原因是因为:适配器需要一个名为Lifecycle的参数。
该参数将Observer连接到父Fragment。
由于适配器需要在视图销毁时“清除”,因此需要这样做。
问题在于没有真正的方法可以实现这一点。
这意味着唯一的方法是:

recyclerView.setAdapter(null);

这个问题在于适配器先前已经将观察者连接到了生命周期:
mLifecycle.addObserver(new LifecycleEventObserver() {

在适配器中持有对mFragments的引用,不仅如此,还包括LifecycleEventObserver捕获的每个字段。

有一个特定的实例是所有泄漏的罪魁祸首,它是在FragmentStateAdapter.class的548行连接的观察者。

mLifecycle.addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            handler.removeCallbacks(runnable);
            source.getLifecycle().removeObserver(this);
        }
    }
});

根据文档,适配器必须使用默认构造函数实例化的默认方式是通过传递fragment.getLifecycle();

泄漏: 情况A。

在将父片段推送到后堆栈中时,Adapter字段实例保留在内部。

解决方案A:仍然使用默认构造函数,但在onViewDestroyed期间必须将Adapter字段实例设置为null,同时将Adapter字段和viewPager.setAdapter设置为NULL。 这个解决方案在场景B中会创建一个问题...

解决方案B:使用非默认构造函数,并传递fragment.getViewLifecycleOwner().getLifeCycle()而不是Fragment。每次在onViewCreated()中构建一个新的适配器,观察者将在onViewDestroyed()自动取消注册。 适配器仍必须设置为null,但vp.setAdapter不需要

情况B:

当使用解决方案A时,父片段被设置为setRetainInstance(true)。

原因:ViewPager提供了一个新的FragmentManager,但是由LifecycleEventObserver捕获的先前Adapter实例将尝试删除具有不再存在的FragmentManager版本的Frgaments。此泄漏出现在配置更改后的10秒钟内。

解决方案:我正在努力解决它,如果找到解决方案,我会更新...

忘记解决方案A(片段构造函数)。 为了使其工作,需要修复片段生命周期,因为阶段是无序的。

首先,在onAttach之前应用onCreate,而在onDetach之后应用onDestroy。 并且在所有形式的事务期间都应保持此顺序,无论是回退栈推/拉/弹出还是配置更改。

第二,应添加2个事件回调步骤到生命周期中,或者说...将它们从当前位置移动:这些事件将是ON_START和ON_STOP,并且应在onAttach和onDetach期间分派。

这些事件应该是定义观察者是否应在配置更改期间分离的事件,而不是onDestroy或onDestroyView。

这将允许:

A)使用Fragment.lifecycle而不是fragment.viewLifecycleOwner生命周期(最初的意图)。

B)在回退栈推送期间防止不必要的观察者断开连接,因为在此操作期间不需要进行观察者垃圾收集。

C)正确地推断由配置更改触发的断开连接,并在此阶段分离观察者。

因此,我的建议是改用viewLifecycleOwner...这样更容易...


0
当 Android 配置发生变化时,比如屏幕旋转,活动和片段将被重新创建,因此您需要清除引用以避免内存泄漏。
例如:
public void onDestroyView() {
    super.onDestroyView();

    if(viewPager != null) {
        viewPager.setAdapter(null);
        viewPager.removeOnPageChangeListener(null);
    }

    if(tabLayout != null) {
        tabLayout.clearOnTabSelectedListeners();
        tabLayout.setupWithViewPager(null);
    }

    viewPager = null;
    tabLayout = null;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接