Bootstrap

关于 VieiwPager2 + FragmentStateAdapter 的内存泄漏问题

D  ┬───
​                             DGC Root: System classD  │
​                             D  ├─ android.os.StrictMode$InstanceTracker classDLeaking: NO (MainActivity↓ is not leaking and a class is never leaking)D  │    ↓ static StrictMode$InstanceTracker.sInstanceCounts
​                             D  ├─ java.util.HashMap instance
​                             DLeaking: NO (MainActivity↓ is not leaking)D  │    ↓ HashMap[key()]D  ├─ .ui.main.MainActivity classDLeaking: NO (MainActivity↓ is not leaking and a class is never leaking)D  │    ↓ static MainActivity.mMainAct
​                             D  ├─ .ui.main.MainActivity instance
​                             DLeaking: NO (LifecycleRegistry↓ is not leaking and Activity#mDestroyed is false)D  │    mApplication instance of .app.MyApplicationD  │    mEmbeddedApplication instance of .app.MyApplicationD  │    mBase instance of androidx.appcompat.view.ContextThemeWrapperD  │    ↓ ComponentActivity.mLifecycleRegistry
​                             D  ├─ androidx.lifecycle.LifecycleRegistry instance
​                             DLeaking: NO (state is RESUMED)D  │    ↓ LifecycleRegistry.mObserverMap
​                             D~~~~~~~~~~~~D  ├─ androidx.arch.core.internal.FastSafeIterableMap instance
​                              DLeaking: UNKNOWNDRetaining 618.4 kB in 10773 objects
​                              D  │    ↓ FastSafeIterableMap[key()]D~~~~~~~D  ├─ androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3 instance
​                              DLeaking: UNKNOWNDRetaining 617.6 kB in 10742 objects
​                              DAnonymous class implementing androidx.lifecycle.LifecycleEventObserverD  │    ↓ FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3.this$1D~~~~~~D  ├─ androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer instance
​                              DLeaking: UNKNOWNDRetaining 617.6 kB in 10741 objects
​                              D  │    ↓ FragmentStateAdapter$FragmentMaxLifecycleEnforcer.this$0D~~~~~~D  ├─ .ui.schedule.adapter.WeekPagerAdapter instance
​                              DLeaking: UNKNOWNDRetaining 3.9 kB in 172 objects
​                              D  │    ↓ WeekNotePagerAdapter.fragment
​                              D~~~~~~~~D  ╰→ .ui.schedule.fragment.WeekFragment instance
​                              D       Leaking: YES (ObjectWatcher was watching this because .ui.schedule.fragment.D       WeekFragment received Fragment#onDestroy() callback. Conflicts with Fragment.mLifecycleRegistry.state
​                              D       is INITIALIZED)


根据以上信息,定位到问题代码

 D  ├─ androidx.arch.core.internal.FastSafeIterableMap instance
​                              DLeaking: UNKNOWNDRetaining 618.4 kB in 10773 objects
​                              D  │    ↓ FastSafeIterableMap[key()]D~~~~~~~D  ├─ androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3 instance
​                              DLeaking: UNKNOWNDRetaining 617.6 kB in 10742 objects
​                              DAnonymous class implementing androidx.lifecycle.LifecycleEventObserverD  │    ↓ FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3.this$1D~~~~~~D  ├─ androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer instance
​                              DLeaking: UNKNOWNDRetaining 617.6 kB in 10741 objects
​                              D  │    ↓ FragmentStateAdapter$FragmentMaxLifecycleEnforcer.this$0D~~~~~~D  ├─ .ui.schedule.adapter.WeekPagerAdapter instance
​                              DLeaking: UNKNOWNDRetaining 3.9 kB in 172 objects
​                              D  │    ↓ WeekNotePagerAdapter.fragment
​                              D~~~~~~~~D  ╰→ .ui.schedule.fragment.WeekFragment instance

那么我们先去看一下我们的 WeekNotePagerAdapter 有什么操作,


class WeekNotePagerAdapter(
    fm: FragmentActivity, private var mWeekTimestamps: List<Long>,var fragment:WeekNoteScheduleFragment
) : FragmentStateAdapter(fm) {
  
    val mFragments = SparseArray<ItemWeekNoteFragment>()
    private val mPresenter = QuadrantPresenter()


    fun setDatas(datas: List<Long>) {
        this.mWeekTimestamps = datas
    }

    override fun containsItem(itemId: Long): Boolean {
        return mWeekTimestamps.contains(itemId)
    }

    override fun getItemId(position: Int): Long {
        return mWeekTimestamps[position]
    }


    override fun getItemCount(): Int {
        return mWeekTimestamps.size
    }

    override fun createFragment(position: Int): Fragment {
        val bundle = Bundle()
        val weekTimestamp = mWeekTimestamps[position]
        bundle.putLong(WEEK_START_TIMESTAMP, weekTimestamp)

        val fragment = ItemWeekNoteFragment(fragment)
        fragment.arguments = bundle

        mFragments.put(position, fragment)
        return fragment
    }

    fun removeFragment(frg: ItemWeekNoteFragment) {
        var key = mFragments.indexOfValue(frg)
        mFragments.remove(key)
    }
}

可以看到,我们调用的adapter 继承自 FragmentStateAdapter,那么我们去看一下 FragmentStateAdapterFragmentMaxLifecycleEnforcer这个类型对应的参数.

public abstract class FragmentStateAdapter extends
        RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter {
	//....
    private FragmentMaxLifecycleEnforcer mFragmentMaxLifecycleEnforcer;

    @CallSuper
    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        checkArgument(mFragmentMaxLifecycleEnforcer == null);
        mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
        mFragmentMaxLifecycleEnforcer.register(recyclerView);
    }

    @CallSuper
    @Override
    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
        mFragmentMaxLifecycleEnforcer.unregister(recyclerView);
        mFragmentMaxLifecycleEnforcer = null;
    }
    //.....
}

可以看到 FragmentStateAdapter.mFragmentMaxLifecycleEnforcer该参数的使用在这两个方法中,我们通过在 WeekNotePagerAdapter 重写 onDetachedFromRecyclerView方法,并且在该方法内添加 Log,发现当发生内存泄漏时,该方法并没有被调用。

经过查找资料:发现如果RecyclerView没有正确分离或者释放,那么onDetachedFromRecyclerView就不会被调用。

解决方法一:

因为ViewPager2 中的 mRecyclerView 仅包访问,所以,我们可以通过反射的方式,在该页面的onDestroyView()中
进行手动调用。

 		val viewPage2 = mBinding?.mViewPager
        if (viewPage2 != null) {
            val mRecyclerView = viewPage2::class.java.getDeclaredField("mRecyclerView")
            mRecyclerView.isAccessible = true
            val recyclerView = mRecyclerView.get(viewPage2) as RecyclerView
            mWeekNoteAdapter?.onDetachedFromRecyclerView(recyclerView)
        }

解决方式二:

    override fun onDestroyView() {
        super.onDestroyView()
        mBinding?.mViewPager?.adapter = null
        mWeekNoteAdapter?.mFragments?.clear()
    }

;