【问题标题】:Leak Canary detects memory leaks for TabLayout with ViewPager2Leak Canary 使用 ViewPager2 检测 TabLayout 的内存泄漏
【发布时间】:2020-08-29 22:47:45
【问题描述】:

我按照官方文档使用 ViewPager2 设置了 TabLayout。我以这种方式使用 TabLayoutMediator 将 TabLayout 与 ViewPager2 连接起来:

TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
        tab.setIcon(getTabIcon(position))
        tab.text = getTabTitle(position)
    }.attach()

但是 Leak Canary 检测到似乎与 TabLayout 和 TabLayoutMediator 相关的内存泄漏。日志如下:

2020-05-13 17:57:32.914 D/LeakCanary: ┬───
2020-05-13 17:57:32.914 D/LeakCanary: │ GC Root: Local variable in native code
2020-05-13 17:57:32.914 D/LeakCanary: │
2020-05-13 17:57:32.914 D/LeakCanary: ├─ android.net.ConnectivityThread instance
2020-05-13 17:57:32.914 D/LeakCanary: │    Leaking: NO (PathClassLoader↓ is not leaking)
2020-05-13 17:57:32.914 D/LeakCanary: │    Thread name: 'ConnectivityThread'
2020-05-13 17:57:32.914 D/LeakCanary: │    ↓ ConnectivityThread.contextClassLoader
2020-05-13 17:57:32.914 D/LeakCanary: ├─ dalvik.system.PathClassLoader instance
2020-05-13 17:57:32.914 D/LeakCanary: │    Leaking: NO (ViewDataBinding↓ is not leaking and A ClassLoader is never leaking)
2020-05-13 17:57:32.914 D/LeakCanary: │    ↓ PathClassLoader.runtimeInternalObjects
2020-05-13 17:57:32.914 D/LeakCanary: ├─ java.lang.Object[] array
2020-05-13 17:57:32.914 D/LeakCanary: │    Leaking: NO (ViewDataBinding↓ is not leaking)
2020-05-13 17:57:32.914 D/LeakCanary: │    ↓ Object[].[349]
2020-05-13 17:57:32.914 D/LeakCanary: ├─ androidx.databinding.ViewDataBinding class
2020-05-13 17:57:32.914 D/LeakCanary: │    Leaking: NO (RouteInfoFragment↓ is not leaking and a class is never leaking)
2020-05-13 17:57:32.914 D/LeakCanary: │    ↓ static ViewDataBinding.sReferenceQueue
2020-05-13 17:57:32.914 D/LeakCanary: ├─ java.lang.ref.ReferenceQueue instance
2020-05-13 17:57:32.914 D/LeakCanary: │    Leaking: NO (RouteInfoFragment↓ is not leaking)
2020-05-13 17:57:32.914 D/LeakCanary: │    ↓ ReferenceQueue.head
2020-05-13 17:57:32.914 D/LeakCanary: ├─ androidx.databinding.ViewDataBinding$WeakListener instance
2020-05-13 17:57:32.914 D/LeakCanary: │    Leaking: NO (RouteInfoFragment↓ is not leaking)
2020-05-13 17:57:32.914 D/LeakCanary: │    ↓ ViewDataBinding$WeakListener.mObservable
2020-05-13 17:57:32.914 D/LeakCanary: ├─ androidx.databinding.ViewDataBinding$LiveDataListener instance
2020-05-13 17:57:32.914 D/LeakCanary: │    Leaking: NO (RouteInfoFragment↓ is not leaking)
2020-05-13 17:57:32.914 D/LeakCanary: │    ↓ ViewDataBinding$LiveDataListener.mLifecycleOwner
2020-05-13 17:57:32.914 D/LeakCanary: ├─ iclaude.berlinwanderer.features.route.ui.route_dashboard.route_info.RouteInfoFragment instance
2020-05-13 17:57:32.915 D/LeakCanary: │    Leaking: NO (RouteDashboardFragment↓ is not leaking and Fragment#mFragmentManager is not null)
2020-05-13 17:57:32.915 D/LeakCanary: │    Fragment.mTag=f0
2020-05-13 17:57:32.915 D/LeakCanary: │    ↓ RouteInfoFragment.mParentFragment
2020-05-13 17:57:32.915 D/LeakCanary: ├─ iclaude.berlinwanderer.features.route.ui.route_dashboard.RouteDashboardFragment instance
2020-05-13 17:57:32.915 D/LeakCanary: │    Leaking: NO (Fragment#mFragmentManager is not null)
2020-05-13 17:57:32.915 D/LeakCanary: │    ↓ RouteDashboardFragment.mLifecycleRegistry
2020-05-13 17:57:32.915 D/LeakCanary: │                             ~~~~~~~~~~~~~~~~~~
2020-05-13 17:57:32.915 D/LeakCanary: ├─ androidx.lifecycle.LifecycleRegistry instance
2020-05-13 17:57:32.915 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.915 D/LeakCanary: │    ↓ LifecycleRegistry.mObserverMap
2020-05-13 17:57:32.915 D/LeakCanary: │                        ~~~~~~~~~~~~
2020-05-13 17:57:32.915 D/LeakCanary: ├─ androidx.arch.core.internal.FastSafeIterableMap instance
2020-05-13 17:57:32.915 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.915 D/LeakCanary: │    ↓ FastSafeIterableMap.mHashMap
2020-05-13 17:57:32.915 D/LeakCanary: │                          ~~~~~~~~
2020-05-13 17:57:32.915 D/LeakCanary: ├─ java.util.HashMap instance
2020-05-13 17:57:32.915 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.915 D/LeakCanary: │    ↓ HashMap.table
2020-05-13 17:57:32.915 D/LeakCanary: │              ~~~~~
2020-05-13 17:57:32.915 D/LeakCanary: ├─ java.util.HashMap$Node[] array
2020-05-13 17:57:32.915 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.915 D/LeakCanary: │    ↓ HashMap$Node[].[1]
2020-05-13 17:57:32.915 D/LeakCanary: │                     ~~~
2020-05-13 17:57:32.915 D/LeakCanary: ├─ java.util.HashMap$Node instance
2020-05-13 17:57:32.915 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.915 D/LeakCanary: │    ↓ HashMap$Node.key
2020-05-13 17:57:32.915 D/LeakCanary: │                   ~~~
2020-05-13 17:57:32.915 D/LeakCanary: ├─ androidx.viewpager2.adapter.FragmentStateAdapter$5 instance
2020-05-13 17:57:32.915 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.915 D/LeakCanary: │    Anonymous class implementing androidx.lifecycle.LifecycleEventObserver
2020-05-13 17:57:32.915 D/LeakCanary: │    ↓ FragmentStateAdapter$5.this$0
2020-05-13 17:57:32.916 D/LeakCanary: │                             ~~~~~~
2020-05-13 17:57:32.916 D/LeakCanary: ├─ iclaude.berlinwanderer.features.route.ui.route_dashboard.RouteDashboardViewPagerAdapter instance
2020-05-13 17:57:32.916 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.916 D/LeakCanary: │    ↓ RouteDashboardViewPagerAdapter.mObservable
2020-05-13 17:57:32.916 D/LeakCanary: │                                     ~~~~~~~~~~~
2020-05-13 17:57:32.916 D/LeakCanary: ├─ androidx.recyclerview.widget.RecyclerView$AdapterDataObservable instance
2020-05-13 17:57:32.916 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.916 D/LeakCanary: │    ↓ RecyclerView$AdapterDataObservable.mObservers
2020-05-13 17:57:32.916 D/LeakCanary: │                                         ~~~~~~~~~~
2020-05-13 17:57:32.916 D/LeakCanary: ├─ java.util.ArrayList instance
2020-05-13 17:57:32.916 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.916 D/LeakCanary: │    ↓ ArrayList.elementData
2020-05-13 17:57:32.916 D/LeakCanary: │                ~~~~~~~~~~~
2020-05-13 17:57:32.916 D/LeakCanary: ├─ java.lang.Object[] array
2020-05-13 17:57:32.916 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.916 D/LeakCanary: │    ↓ Object[].[0]
2020-05-13 17:57:32.916 D/LeakCanary: │               ~~~
2020-05-13 17:57:32.916 D/LeakCanary: ├─ com.google.android.material.tabs.TabLayoutMediator$PagerAdapterObserver instance
2020-05-13 17:57:32.916 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.916 D/LeakCanary: │    ↓ TabLayoutMediator$PagerAdapterObserver.this$0
2020-05-13 17:57:32.916 D/LeakCanary: │                                             ~~~~~~
2020-05-13 17:57:32.916 D/LeakCanary: ├─ com.google.android.material.tabs.TabLayoutMediator instance
2020-05-13 17:57:32.916 D/LeakCanary: │    Leaking: UNKNOWN
2020-05-13 17:57:32.916 D/LeakCanary: │    ↓ TabLayoutMediator.tabLayout
2020-05-13 17:57:32.916 D/LeakCanary: │                        ~~~~~~~~~
2020-05-13 17:57:32.916 D/LeakCanary: ├─ com.google.android.material.tabs.TabLayout instance
2020-05-13 17:57:32.916 D/LeakCanary: │    Leaking: YES (View detached and has parent)
2020-05-13 17:57:32.916 D/LeakCanary: │    mContext instance of iclaude.berlinwanderer.features.route.ui.RouteActivity with mDestroyed = false
2020-05-13 17:57:32.916 D/LeakCanary: │    View#mParent is set
2020-05-13 17:57:32.916 D/LeakCanary: │    View#mAttachInfo is null (view detached)
2020-05-13 17:57:32.916 D/LeakCanary: │    View.mID = R.id.tab_layout
2020-05-13 17:57:32.916 D/LeakCanary: │    View.mWindowAttachCount = 1
2020-05-13 17:57:32.916 D/LeakCanary: │    ↓ TabLayout.mParent
2020-05-13 17:57:32.916 D/LeakCanary: ╰→ androidx.constraintlayout.motion.widget.MotionLayout instance
2020-05-13 17:57:32.916 D/LeakCanary: ​     Leaking: YES (ObjectWatcher was watching this because iclaude.berlinwanderer.features.route.ui.route_dashboard.RouteDashboardFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
2020-05-13 17:57:32.916 D/LeakCanary: ​     key = 9c28ffc6-b1ce-4316-b015-c4df278892a1
2020-05-13 17:57:32.916 D/LeakCanary: ​     watchDurationMillis = 36154
2020-05-13 17:57:32.916 D/LeakCanary: ​     retainedDurationMillis = 31130
2020-05-13 17:57:32.916 D/LeakCanary: ​     mContext instance of iclaude.berlinwanderer.features.route.ui.RouteActivity with mDestroyed = false
2020-05-13 17:57:32.917 D/LeakCanary: ​     View#mParent is null
2020-05-13 17:57:32.917 D/LeakCanary: ​     View#mAttachInfo is null (view detached)
2020-05-13 17:57:32.917 D/LeakCanary: ​     View.mID = R.id.ml_main
2020-05-13 17:57:32.917 D/LeakCanary: ​     View.mWindowAttachCount = 1

有什么问题?如何解决?

【问题讨论】:

    标签: android memory-leaks android-tablayout leakcanary


    【解决方案1】:

    我认为这是一个错误,它在 google 上列出。

    https://issuetracker.google.com/issues/151212195 https://issuetracker.google.com/issues/154751401

    cmets 中提到了一个似乎对我有用的解决方案。让你的 FragmentStateAdapter 用户成为构造函数

     public FragmentStateAdapter(@NonNull FragmentManager fragmentManager,
                @NonNull Lifecycle lifecycle) {
    

    从您的容器片段中创建适配器,如下所示:

    FragmentManager fm = getChildFragmentManager();
    Lifecycle lifecycle = getViewLifecycleOwner().getLifecycle();
    fragmentAdapter = new FragmentAdapter(fm, lifecycle);
    

    【讨论】:

    • 这是迄今为止最好的解决方案。最佳实践仍然表明在TabLayoutMediator 上调用detach(),可能在FragmentonDestroyView() 中,但是应该不需要手动清空实例,这是该解决方案避免的。
    • 我一直在使用 FragmentStateAdapter(Fragment fragment) 构造函数,它又调用了你提到的构造函数。但重要的区别是它是用片段的生命周期调用的,而不是视图的生命周期。在明确传递 viewLifecycleOwner 之后,泄漏对我来说消失了。
    • 只有在使用 Fragment 甚至 Activity 时才会出现这种情况吗?
    【解决方案2】:

    您需要在 onDestroyView 中分离并设置 mediator 为 null:

    class YourFragment: Fragment() {
        
        private var mediator: TabLayoutMediator? = null
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            mediator = TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
                tab.setIcon(getTabIcon(position))
                tab.text = getTabTitle(position)
            }
            mediator.attach()
        }
    
        override fun onDestroyView() {
            super.onDestroyView()
            mediator?.detach()
            mediator = null
            binding.viewpager.adapter = null
            _binding = null
        }
    
    }
    

    【讨论】:

      【解决方案3】:

      我有同样的类似问题,您需要在 onDestroyView 中将 viewPager 适配器设置为 null,然后如果您使用数据绑定,请确保您将绑定设置为 null。祝你好运

      【讨论】:

      • 我找到了解决方案。似乎在 onDestroyView 中,您必须将 viewpager.adaper 字段、适配器本身、TabLauoutMediator(分离后)和绑定类引用设置为空。我希望将来官方文档变得更加清晰。
      【解决方案4】:

      即使在 RouteActivity 被销毁(或重新创建)之后,被 RouteActivity 暴露的 mContext 仍然存在。发生这种情况可能是因为某些东西正在使用它。跟踪正在使用 mContext 的内容并停止它。

      【讨论】:

      • RouteActivity 是持有 Fragments 的 Activity,所以它不会被销毁或重新创建。
      猜你喜欢
      • 2019-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-20
      • 2011-04-03
      • 2012-07-16
      • 1970-01-01
      相关资源
      最近更新 更多