为什么在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
我有一个带有视图寻呼机的屏幕,我在其中使用同一片段的多个片段实例 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