为什么在Fragment中使用lifecycleScope在ViewPager2中使用会导致内存泄漏?

Why using lifecycleScope in Fragment causes memory leak when used in ViewPager2?

我有一个带有视图寻呼机的屏幕,我在其中使用同一片段的多个片段实例 class,这些片段实例使用 Paging 3 从服务器加载数据。

在onViewCreated里面我有这个功能,

onViewCreated() {
   fun updateList()
}

fun updateList() {
       lifecycleScope.launch {
            viewModel.orders.collectLatest {
                adapter?.submitData(it)
            }
        }
}

上面的函数给我带来了内存泄漏,找不到解决方案并试了一下,它成功了。仍然无法找到它起作用的原因或这是否是正确的方法。

viewLifecycleOwner.lifecycleScope.launch {
            viewModel.orders.collectLatest {
                adapter?.submitData(it)
            }
}

泄漏Logcat痕迹:

D/LeakCanary: ​
D/LeakCanary: ┬───
D/LeakCanary: │ GC Root: Input or output parameters in native code
D/LeakCanary: │
D/LeakCanary: ├─ dalvik.system.PathClassLoader instance
D/LeakCanary: │    Leaking: NO (InternalLeakCanary↓ is not leaking and A ClassLoader is never leaking)
D/LeakCanary: │    ↓ ClassLoader.runtimeInternalObjects
D/LeakCanary: ├─ java.lang.Object[] array
D/LeakCanary: │    Leaking: NO (InternalLeakCanary↓ is not leaking)
D/LeakCanary: │    ↓ Object[950]
D/LeakCanary: ├─ leakcanary.internal.InternalLeakCanary class
D/LeakCanary: │    Leaking: NO (HomeActivity↓ is not leaking and a class is never leaking)
D/LeakCanary: │    ↓ static InternalLeakCanary.resumedActivity
D/LeakCanary: ├─ com.awantunai.app.home.HomeActivity instance
D/LeakCanary: │    Leaking: NO (Activity#mDestroyed is false)
D/LeakCanary: │    mApplication instance of com.awantunai.app.base.AwanApplication
D/LeakCanary: │    mBase instance of androidx.appcompat.view.ContextThemeWrapper
D/LeakCanary: │    ↓ ComponentActivity.mActivityResultRegistry
D/LeakCanary: │                        ~~~~~~~~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ androidx.activity.ComponentActivity instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 348,5 kB in 8676 objects
D/LeakCanary: │    Anonymous subclass of androidx.activity.result.ActivityResultRegistry
D/LeakCanary: │    this[=12=] instance of com.awantunai.app.home.HomeActivity with mDestroyed = false
D/LeakCanary: │    ↓ ActivityResultRegistry.mKeyToLifecycleContainers
D/LeakCanary: │                             ~~~~~~~~~~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ java.util.HashMap instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 504 B in 18 objects
D/LeakCanary: │    ↓ HashMap["fragment_978a66c7-ff9d-435c-a477-d49cb3df918d_rq#0"]
D/LeakCanary: │             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ androidx.activity.result.ActivityResultRegistry$LifecycleContainer instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 76 B in 3 objects
D/LeakCanary: │    ↓ ActivityResultRegistry$LifecycleContainer.mLifecycle
D/LeakCanary: │                                                ~~~~~~~~~~
D/LeakCanary: ├─ androidx.lifecycle.LifecycleRegistry instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 84,5 kB in 2074 objects
D/LeakCanary: │    ↓ Lifecycle.mInternalScopeRef
D/LeakCanary: │                ~~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ java.util.concurrent.atomic.AtomicReference instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 12 B in 1 objects
D/LeakCanary: │    ↓ AtomicReference.value
D/LeakCanary: │                      ~~~~~
D/LeakCanary: ├─ androidx.lifecycle.LifecycleCoroutineScopeImpl instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 83,7 kB in 2042 objects
D/LeakCanary: │    ↓ LifecycleCoroutineScopeImpl.coroutineContext
D/LeakCanary: │                                  ~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ kotlin.coroutines.CombinedContext instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 83,7 kB in 2041 objects
D/LeakCanary: │    ↓ CombinedContext.left
D/LeakCanary: │                      ~~~~
D/LeakCanary: ├─ kotlinx.coroutines.SupervisorJobImpl instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 83,7 kB in 2040 objects
D/LeakCanary: │    ↓ JobSupport._state
D/LeakCanary: │                 ~~~~~~
D/LeakCanary: ├─ kotlinx.coroutines.ChildHandleNode instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 83,7 kB in 2039 objects
D/LeakCanary: │    ↓ ChildHandleNode.childJob
D/LeakCanary: │                      ~~~~~~~~
D/LeakCanary: ├─ kotlinx.coroutines.StandaloneCoroutine instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 83,6 kB in 2038 objects
D/LeakCanary: │    ↓ JobSupport._state
D/LeakCanary: │                 ~~~~~~
D/LeakCanary: ├─ kotlinx.coroutines.ChildHandleNode instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 83,6 kB in 2036 objects
D/LeakCanary: │    ↓ ChildHandleNode.childJob
D/LeakCanary: │                      ~~~~~~~~
D/LeakCanary: ├─ kotlinx.coroutines.internal.ScopeCoroutine instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 83,6 kB in 2035 objects
D/LeakCanary: │    ↓ ScopeCoroutine.uCont
D/LeakCanary: │                     ~~~~~
D/LeakCanary: ├─ com.awantunai.app.home.cart.v3.OrderListFragment$updateAdapter instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 61,6 kB in 1246 objects
D/LeakCanary: │    Anonymous subclass of kotlin.coroutines.jvm.internal.SuspendLambda
D/LeakCanary: │    ↓ OrderListFragment$updateAdapter.$adapter
D/LeakCanary: │                                        ~~~~~~~~
D/LeakCanary: ├─ com.awantunai.app.home.cart.v3.OrderAdapter instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 61,6 kB in 1244 objects
D/LeakCanary: │    ↓ RecyclerView$Adapter.mObservable
D/LeakCanary: │                           ~~~~~~~~~~~
D/LeakCanary: ├─ androidx.recyclerview.widget.RecyclerView$AdapterDataObservable instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 61,0 kB in 1218 objects
D/LeakCanary: │    ↓ Observable.mObservers
D/LeakCanary: │                 ~~~~~~~~~~
D/LeakCanary: ├─ java.util.ArrayList instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 60,9 kB in 1217 objects
D/LeakCanary: │    ↓ ArrayList[0]
D/LeakCanary: │               ~~~
D/LeakCanary: ├─ androidx.recyclerview.widget.RecyclerView$RecyclerViewDataObserver instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 60,9 kB in 1215 objects
D/LeakCanary: │    ↓ RecyclerView$RecyclerViewDataObserver.this[=12=]
D/LeakCanary: │                                            ~~~~~~
D/LeakCanary: ├─ androidx.recyclerview.widget.RecyclerView instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 60,9 kB in 1214 objects
D/LeakCanary: │    View not part of a window view hierarchy
D/LeakCanary: │    View.mAttachInfo is null (view detached)
D/LeakCanary: │    View.mID = R.id.rv_orders
D/LeakCanary: │    View.mWindowAttachCount = 1
D/LeakCanary: │    mContext instance of com.awantunai.app.home.HomeActivity with mDestroyed = false
D/LeakCanary: │    ↓ View.mParent
D/LeakCanary: │           ~~~~~~~
D/LeakCanary: ├─ androidx.swiperefreshlayout.widget.SwipeRefreshLayout instance
D/LeakCanary: │    Leaking: UNKNOWN
D/LeakCanary: │    Retaining 57,2 kB in 1113 objects
D/LeakCanary: │    View not part of a window view hierarchy
D/LeakCanary: │    View.mAttachInfo is null (view detached)
D/LeakCanary: │    View.mID = R.id.swipe_to_refresh
D/LeakCanary: │    View.mWindowAttachCount = 1
D/LeakCanary: │    mContext instance of com.awantunai.app.home.HomeActivity with mDestroyed = false
D/LeakCanary: │    ↓ View.mParent
D/LeakCanary: │           ~~~~~~~
D/LeakCanary: ╰→ androidx.coordinatorlayout.widget.CoordinatorLayout instance
D/LeakCanary: ​     Leaking: YES (ObjectWatcher was watching this because com.awantunai.app.home.cart.v3.OrderListFragment received
D/LeakCanary: ​     Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
D/LeakCanary: ​     Retaining 52,8 kB in 1015 objects
D/LeakCanary: ​     key = 80c7cbe8-db9d-49d9-8607-03f6b9e662bc
D/LeakCanary: ​     watchDurationMillis = 5964
D/LeakCanary: ​     retainedDurationMillis = 949
D/LeakCanary: ​     View not part of a window view hierarchy
D/LeakCanary: ​     View.mAttachInfo is null (view detached)
D/LeakCanary: ​     View.mID = R.id.parentLayout
D/LeakCanary: ​     View.mWindowAttachCount = 1
D/LeakCanary: ​     mContext instance of com.awantunai.app.home.HomeActivity with mDestroyed = false
D/LeakCanary: 
D/LeakCanary: METADATA
D/LeakCanary: 
D/LeakCanary: Build.VERSION.SDK_INT: 32
D/LeakCanary: Build.MANUFACTURER: Google
D/LeakCanary: LeakCanary version: 2.8.1
D/LeakCanary: App process name: com.awantunai.app.alpha
D/LeakCanary: Stats: LruCache[maxSize=3000,hits=134631,misses=245082,hitRate=35%]
D/LeakCanary: RandomAccess[bytes=14017705,reads=245082,travel=175631031479,range=69026744,size=98758092]
D/LeakCanary: Analysis duration: 25897 ms

做了一些研究并意识到 viewLifeCycleOwner.lifecycleScope 绑定到从 onCreateView 到 onDestroyView 的生命周期。

另一方面,如果我们只使用 this.lifecycleScope,这个范围将从 onAttach 开始绑定到 onDetach。

在上面的表达式中,假设当我移动到另一个片段时视图被破坏了,之后在调用 onDetach 之前我得到了响应

viewModel.orders.collectLatest {
adapter?.submitData(it)
}

当 fragment 的视图被销毁时,这将访问 recyclerview,因为它持有对适配器的引用并导致内存泄漏

这里有一些参考: https://cs-ibrahimyilmaz.medium.com/viewlifecycleowner-vs-this-a8259800367b