【问题标题】:FragmentStatePagerAdapter memory leak (nested fragments with viewpager)FragmentStatePagerAdapter 内存泄漏(带有 viewpager 的嵌套片段)
【发布时间】:2014-01-17 05:35:07
【问题描述】:

我的适配器中有一个“内存泄漏”(引用将在后面解释)。我目前正在使用嵌套片段来托管 viewpager。

我的设置如下:
1. Activity(承载 Fragment A 的空 Activity)
2. 片段 A - 使用 Fragmentstatepageradapter 托管 viewpager 的片段。每个浏览器页面都承载片段 B。
3. Fragment B - 一个包含 imageview 的片段。

一切都很好,除非发生配置更改。监控堆,似乎每次旋转都会增长 100 kb。手动 GCing 不会释放内存。

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

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

有什么想法吗?将不胜感激。

谢谢, JC

【问题讨论】:

    标签: android memory-leaks android-viewpager fragmentstatepageradapter


    【解决方案1】:

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

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

    如果这也解决了您的问题,请告诉我。

    【讨论】:

    • 谢谢你!我自己永远也想不通(尽管这是合乎逻辑的)。
    • 应该被标记为正确的遮阳篷。谢谢!
    • 你能告诉我在哪里放 getChildFragmentManager() 我正在执行以下操作: MainActivity: fragmentAdapter = new FragmentAdapter(getSupportFragmentManager());并在片段适配器中: public FragmentAdapter(FragmentManager fm) { super(fm); }
    • 请在您的 MainActivity 中尝试以下操作:fragmentAdapter = new FragmentAdapter(getChildFragmentManager());
    • 出色的修复,非常感谢。
    【解决方案2】:

    我有类似的问题,我用ViewPager2,需要用 getChildFragmentManager() 而不是 getSupportFragmentManager(),因为我希望 PageFragment 使用requireParentFragment() 引用父片段(承载ViewPager

    注意当我使用getSupportFragmentManager() 时,我得到了java.lang.IllegalStateException Fragment PageFragment{f152edf} (af30cf2b-acf1-4930-9b83-03ac8144cfc6) f49} is not a child Fragment

    我使用getChildFragmentManager() 的另一个原因是,因为我正在使用导航组件,所以我不需要使用导航组件的相同片段管理器getSupportFragmentManager() 来管理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());
    

    【讨论】:

      【解决方案3】:

      当 Android 配置发生变化时,例如屏幕旋转,Activity 和 Fragment 会重新创建,因此您需要干净的引用以避免内存泄漏。

      例如:

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

      【讨论】:

        【解决方案4】:

        我有同样的问题。我设置了 null TabLayoutMediator 值,但此后泄漏并没有消失,我在 onDestroyView 中分离了 TabLayoutMediator,它对我有用。

        override fun onDestroyView() {
            super.onDestroyView()
        
            mediator?.detach()
            mediator = null
            _binding = null
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-11-05
          • 1970-01-01
          • 2013-11-30
          相关资源
          最近更新 更多