2

I have a basic navigation structure that starts with a MainFragment, which has a ViewPager2 & TabLayout that are connected with a TabLayoutMediator.

The layout is as follows:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true">

        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/view_pager"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintBottom_toTopOf="@+id/tabs"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/tabs"
            android:layout_width="match_parent"
            android:layout_height="@dimen/tab_menu_size"
            android:layout_gravity="bottom"
            android:background="?android:attr/windowBackground"
            android:elevation="@dimen/elevation_normal"
            app:layout_constraintBottom_toBottomOf="parent"
            app:tabGravity="fill"
            app:tabIconTint="?android:attr/colorPrimary"
            app:tabIconTintMode="src_in"
            app:tabIndicator="@drawable/indicator"
            app:tabIndicatorColor="?android:attr/colorPrimary"
            app:tabMode="fixed"
            app:tabRippleColor="@color/selection_color" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Thats created using this function:

private var binding: FragmentMainBinding? = null

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    binding = FragmentMainBinding.inflate(inflater)

    binding!!.lifecycleOwner = viewLifecycleOwner

    binding!!.viewPager.adapter = PagerAdapter(requireActivity())

    // Link the ViewPager & Tab View
    TabLayoutMediator(binding!!.tabs, binding!!.viewPager) { tab, position ->
        tab.icon = ContextCompat.getDrawable(requireContext(), tabIcons[position])
    }.attach()

    Log.d(this::class.simpleName, "Fragment Created.")

    return binding!!.root
}

From the MainFragment, you can navigate to ArtistDetailFragment, which just contains a TextView.

The problem seems to be when one navigates from MainFragment to ArtistDetailFragment, a reference to the ViewPager2 leaks according to LeakCanary:

    ┬───
    │ GC Root: System class
    │
    ├─ android.app.ActivityThread class
    │    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
    │    ↓ static ActivityThread.sCurrentActivityThread
    ├─ android.app.ActivityThread instance
    │    Leaking: NO (MainActivity↓ is not leaking)
    │    ↓ ActivityThread.mActivities
    ├─ android.util.ArrayMap instance
    │    Leaking: NO (MainActivity↓ is not leaking)
    │    ↓ ArrayMap.mArray
    ├─ java.lang.Object[] array
    │    Leaking: NO (MainActivity↓ is not leaking)
    │    ↓ Object[].[1]
    ├─ android.app.ActivityThread$ActivityClientRecord instance
    │    Leaking: NO (MainActivity↓ is not leaking)
    │    ↓ ActivityThread$ActivityClientRecord.activity
    ├─ org.oxycblt.auxio.MainActivity instance
    │    Leaking: NO (Activity#mDestroyed is false)
    │    ↓ MainActivity.mLifecycleRegistry
    │                   ~~~~~~~~~~~~~~~~~~
    ├─ androidx.lifecycle.LifecycleRegistry instance
    │    Leaking: UNKNOWN
    │    ↓ LifecycleRegistry.mObserverMap
    │                        ~~~~~~~~~~~~
    ├─ androidx.arch.core.internal.FastSafeIterableMap instance
    │    Leaking: UNKNOWN
    │    ↓ FastSafeIterableMap.mHashMap
    │                          ~~~~~~~~
    ├─ java.util.HashMap instance
    │    Leaking: UNKNOWN
    │    ↓ HashMap.table
    │              ~~~~~
    ├─ java.util.HashMap$Node[] array
    │    Leaking: UNKNOWN
    │    ↓ HashMap$Node[].[9]
    │                     ~~~
    ├─ java.util.HashMap$Node instance
    │    Leaking: UNKNOWN
    │    ↓ HashMap$Node.key
    │                   ~~~
    ├─ androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3 instance
    │    Leaking: UNKNOWN
    │    Anonymous class implementing androidx.lifecycle.LifecycleEventObserver
    │    ↓ FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3.this$1
    │                                                          ~~~~~~
    ├─ androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer instance
    │    Leaking: UNKNOWN
    │    ↓ FragmentStateAdapter$FragmentMaxLifecycleEnforcer.mViewPager
    │                                                        ~~~~~~~~~~
    ├─ androidx.viewpager2.widget.ViewPager2 instance
    │    Leaking: YES (View detached and has parent)
    │    mContext instance of org.oxycblt.auxio.MainActivity with mDestroyed = false
    │    View#mParent is set
    │    View#mAttachInfo is null (view detached)
    │    View.mID = R.id.view_pager
    │    View.mWindowAttachCount = 1
    │    ↓ ViewPager2.mParent
    ╰→ android.widget.LinearLayout instance
    ​     Leaking: YES (ObjectWatcher was watching this because org.oxycblt.auxio.MainFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
    ​     key = 0c34604a-80e9-4191-a647-d12e74ec8009
    ​     watchDurationMillis = 11086
    ​     retainedDurationMillis = 6083
    ​     mContext instance of org.oxycblt.auxio.MainActivity with mDestroyed = false
    ​     View#mParent is null
    ​     View#mAttachInfo is null (view detached)
    ​     View.mWindowAttachCount = 1

Ive tried clearing the reference to the binding object in onDestroyView() from This Answer:

override fun onDestroyView() {
    super.onDestroyView()

    binding = null
}

But that doesnt seem to solve the memory leak either.

Ive also tried clearing the ViewPager's adapter from This Answer but that causes a crash if I navigate back to MainFragment. Is there anything I can do here?

ntorstio
  • 313
  • 2
  • 13
  • Oops. This seems to be a duplicate of an [Existing question](https://stackoverflow.com/q/62851425/14143986) which already has an answer. – ntorstio Sep 11 '20 at 16:08

0 Answers0