Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Leak Canary detects memory leaks for TabLayout with ViewPager2

I followed the official documentation to setup a TabLayout with ViewPager2. I used the TabLayoutMediator this way to connect the TabLayout with the ViewPager2:

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

However Leak Canary detects a memory leak that seems related to the TabLayout and the TabLayoutMediator. The log is the following:

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

What's the problem? How to solve?

like image 874
iClaude Avatar asked May 13 '20 16:05

iClaude


People also ask

Does leak Canary detect memory leaks in tablayout and tablayoutmediator?

However Leak Canary detects a memory leak that seems related to the TabLayout and the TabLayoutMediator. The log is the following:

How does leakcanary find retained objects?

Figure 3. LeakCanary finds retained objects in the heap dump. For each retained object, LeakCanary finds the path of references that prevents that retained object from being garbage collected: its leak trace. You will learn to analyze a leak trace in the next section: Fixing a memory leak.

How does leakcanary detect memory leaks?

Once LeakCanary is installed, it automatically detects and report memory leaks, in 4 steps: Detecting retained objects. Dumping the heap. Analyzing the heap. Categorizing leaks. 1. Detecting retained objects

How does leakcanary detect leaks in Android?

LeakCanary hooks into the Android lifecycle to automatically detect when activities and fragments are destroyed and should be garbage collected. These destroyed objects are passed to an ObjectWatcher, which holds weak references to them. LeakCanary automatically detects leaks for the following objects:


1 Answers

I think this is a bug, its listed on google.

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

There is a solution mentioned in the comments that seems to be working for me. Make your FragmentStateAdapter user the constructor

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

From your container fragment create the adapter as follows:

FragmentManager fm = getChildFragmentManager();
Lifecycle lifecycle = getViewLifecycleOwner().getLifecycle();
fragmentAdapter = new FragmentAdapter(fm, lifecycle);
like image 76
MobDev Avatar answered Sep 17 '22 01:09

MobDev