繁体   English   中英

Android:带有分页 3 流的 ViewModel 正在泄漏

[英]Android: ViewModel with paging 3 flow is leaking

我的问题是,我持有paging-flow实例的shopViewModel不知何故正在泄漏。 我试图通过将flow转换为livedata来解决这个问题,但这没有任何改变。

视图模型

class ShopViewModel @ViewModelInject constructor(
    private val shopPagingSource: ShopPagingSource,
) : ViewModel() {
    val SHOP_PAGE_CONFIG: PagingConfig = PagingConfig(pageSize = 20, enablePlaceholders = false)

    // As LiveData
    val shopFlow = Pager(SHOP_PAGE_CONFIG) { shopPagingSource }.flow.cachedIn(viewModelScope).asLiveData()

   // Before
    val shopFlow = Pager(SHOP_PAGE_CONFIG) { shopPagingSource }.flow.cachedIn(viewModelScope)
}

分段

@AndroidEntryPoint
class ShopFragment(private val shopListAdapter: ShopAdapter) : Fragment(R.layout.fragment_shop), ShopAdapter.OnItemClickListener {
    private val shopViewModel: ShopViewModel by viewModels()
    private val shopBinding: FragmentShopBinding by viewBinding()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        shopBinding.adapter = shopListAdapter.withLoadStateFooter(ShopLoadAdapter(shopListAdapter::retry))
        shopListAdapter.clickHandler(this)
        collectShopList()
    }
    
    override fun forwardClick(product: @NotNull Product) {
        val action = ShopFragmentDirections.actionShopFragmentToShopItemFragment(product)
        findNavController().navigate(action)
    }
   
    private fun collectShopListWithLiveData() = lifecycleScope.launch {
        shopViewModel.shopFlow.observe(viewLifecycleOwner) {
            lifecycleScope.launch {
                shopListAdapter.submitData(it)
            }
        }
    }

    // Before converting to livedata
    private fun collectShopListWithFlow() = lifecycleScope.launch {
        shopViewModel.shopFlow.collectLatest {
             shopListAdapter.submitData(it)
        }
    }


    // To avoid memory leak from injected adapter
    override fun onDestroyView() {
        requireView().findViewById<RecyclerView>(R.id.rv_shop).adapter = null
        super.onDestroyView()
    }
}

适配器

class ShopAdapter @Inject constructor() : PagingDataAdapter<Product, ShopAdapter.ShopViewHolder>(Companion) {

    private lateinit var clickListener: OnItemClickListener

    companion object: DiffUtil.ItemCallback<Product>() {
        override fun areItemsTheSame(oldItem: Product, newItem: Product): Boolean = oldItem.articelNumber == newItem.articelNumber
        override fun areContentsTheSame(oldItem: Product, newItem: Product): Boolean = oldItem == newItem
    }

    inner class ShopViewHolder(val binding: ShopListItemBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShopAdapter.ShopViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        val binding = ShopListItemBinding.inflate(layoutInflater, parent, false)
        return ShopViewHolder(binding).also {
            binding.mcvProductItem.setOnClickListener { clickListener.forwardClick(binding.product!!) }
        }
    }

    override fun onBindViewHolder(holder: ShopAdapter.ShopViewHolder, position: Int) {
        holder.binding.product = getItem(position) ?: return
        holder.binding.executePendingBindings()
    }

    fun clickHandler(clickEventHandler: OnItemClickListener) {
        clickListener = clickEventHandler
    }

    interface OnItemClickListener {
        fun forwardClick(product: @NotNull Product)
    }
}

主片段工厂

class MainFragmentFactory @Inject constructor(
    // .. other dependencies
    private val shopAdapter: ShopAdapter,
) : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment = when(className) {
        // ... other fragments
        ShopFragment::class.java.name -> ShopFragment(shopAdapter)
        else -> super.instantiate(classLoader, className)
}

分页源

class ShopPagingSource @Inject constructor(
    private val shopRepository: ShopFirebaseRepository,
) : PagingSource<QuerySnapshot, Product>() {

    override suspend fun load(params: LoadParams<QuerySnapshot>): LoadResult<QuerySnapshot, Product> = try {
            withTimeout(SHOP_MAX_LOADING_TIME) {
                val currentPage = params.key ?: shopRepository.getCurrentPage()

                val lastDocumentSnapShot = currentPage.documents[currentPage.size() - 1]

                val nextPage = shopRepository.getNextPage(lastDocumentSnapShot)

                LoadResult.Page(
                    data = currentPage.toObjects(),
                    prevKey = null,
                    nextKey = nextPage
                )
            }
        } catch (e: TimeoutCancellationException) {
            Timber.d("Mediator failed, No Internet Connection")
            LoadResult.Error(e)
        } catch (e: ArrayIndexOutOfBoundsException) {
            Timber.d("Mediator failed, ArrayIndexOutOfBounds")
            LoadResult.Error(e)
        } catch (e: Exception) {
            Timber.d("Mediator failed, Unknown Error: ${e.message.toString()}")
            LoadResult.Error(e)
        }
    }

泄漏金丝雀

D/LeakCanary: ====================================
    HEAP ANALYSIS RESULT
    ====================================
    1 APPLICATION LEAKS
    
    References underlined with "~~~" are likely causes.
    Learn more at https://squ.re/leaks.
    
    2618 bytes retained by leaking objects
    Signature: 944313b4ecbdb77c99682dc8c1646e12e4f37d8
    ┬───
    │ GC Root: Local variable in native code
    │
    ├─ dalvik.system.PathClassLoader instance
    │    Leaking: NO (InternalLeakCanary↓ is not leaking and A ClassLoader is never leaking)
    │    ↓ PathClassLoader.runtimeInternalObjects
    ├─ java.lang.Object[] array
    │    Leaking: NO (InternalLeakCanary↓ is not leaking)
    │    ↓ Object[].[2142]
    ├─ leakcanary.internal.InternalLeakCanary class
    │    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
    │    ↓ static InternalLeakCanary.resumedActivity
    ├─ com.example.app.framework.ui.view.MainActivity instance
    │    Leaking: NO (MainNavHostFragment↓ is not leaking and Activity#mDestroyed is false)
    │    ↓ MainActivity.navController$delegate
    ├─ kotlin.SynchronizedLazyImpl instance
    │    Leaking: NO (MainNavHostFragment↓ is not leaking)
    │    ↓ SynchronizedLazyImpl._value
    ├─ androidx.navigation.NavHostController instance
    │    Leaking: NO (MainNavHostFragment↓ is not leaking)
    │    ↓ NavHostController.mLifecycleOwner
    ├─ com.example.app.framework.ui.view.utils.MainNavHostFragment instance
    │    Leaking: NO (Fragment#mFragmentManager is not null)
    │    ↓ MainNavHostFragment.mainFragmentFactory
    │                          ~~~~~~~~~~~~~~~~~~~
    ├─ com.example.app.framework.ui.view.utils.MainFragmentFactory instance
    │    Leaking: UNKNOWN
    │    ↓ MainFragmentFactory.shopAdapter
    │                          ~~~~~~~~~~~
    ├─ com.example.app.framework.ui.adapter.recyclerview.ShopAdapter instance
    │    Leaking: UNKNOWN
    │    ↓ ShopAdapter.differ
    │                  ~~~~~~
    ├─ androidx.paging.AsyncPagingDataDiffer instance
    │    Leaking: UNKNOWN
    │    ↓ AsyncPagingDataDiffer.differBase
    │                            ~~~~~~~~~~
    ├─ androidx.paging.AsyncPagingDataDiffer$differBase$1 instance
    │    Leaking: UNKNOWN
    │    Anonymous subclass of androidx.paging.PagingDataDiffer
    │    ↓ AsyncPagingDataDiffer$differBase$1.receiver
    │                                         ~~~~~~~~
    ├─ androidx.paging.PageFetcher$PagerUiReceiver instance
    │    Leaking: UNKNOWN
    │    ↓ PageFetcher$PagerUiReceiver.this$0
    │                                  ~~~~~~
    ├─ androidx.paging.PageFetcher instance
    │    Leaking: UNKNOWN
    │    ↓ PageFetcher.pagingSourceFactory
    │                  ~~~~~~~~~~~~~~~~~~~
    ├─ com.example.app.framework.ui.viewmodel.ShopViewModel$shopFlow$1 instance
    │    Leaking: UNKNOWN
    │    Anonymous subclass of kotlin.jvm.internal.Lambda
    │    ↓ ShopViewModel$shopFlow$1.this$0
    │                               ~~~~~~
    ╰→ com.example.app.framework.ui.viewmodel.ShopViewModel instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.example.app.framework.ui.viewmodel.ShopViewModel received ViewModel#onCleared() callback)
    ​     key = 0e65fcab-e6dd-475a-83d4-87b2050d797b
    ​     watchDurationMillis = 7771
    ​     retainedDurationMillis = 2769
    ====================================

编辑

使用@FragmentScopedShopAdapter进行范围界定时, ShopAdapter以下泄漏:

    ┬───
    │ GC Root: Local variable in native code
    │
    ├─ dalvik.system.PathClassLoader instance
    │    Leaking: NO (InternalLeakCanary↓ is not leaking and A ClassLoader is never leaking)
    │    ↓ PathClassLoader.runtimeInternalObjects
    ├─ java.lang.Object[] array
    │    Leaking: NO (InternalLeakCanary↓ is not leaking)
    │    ↓ Object[].[409]
    ├─ leakcanary.internal.InternalLeakCanary class
    │    Leaking: NO (MainActivity↓ is not leaking and a class is never leaking)
    │    ↓ static InternalLeakCanary.resumedActivity
    ├─ com.example.app.framework.ui.view.MainActivity instance
    │    Leaking: NO (MainNavHostFragment↓ is not leaking and Activity#mDestroyed is false)
    │    mApplication instance of com.example.app.App
    │    mBase instance of androidx.appcompat.view.ContextThemeWrapper, not wrapping known Android context
    │    ↓ MainActivity.navController$delegate
    ├─ kotlin.SynchronizedLazyImpl instance
    │    Leaking: NO (MainNavHostFragment↓ is not leaking)
    │    ↓ SynchronizedLazyImpl._value
    ├─ androidx.navigation.NavHostController instance
    │    Leaking: NO (MainNavHostFragment↓ is not leaking)
    │    mActivity instance of com.example.app.framework.ui.view.MainActivity with mDestroyed = false
    │    mContext instance of dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper, wrapping
    │    activity com.example.app.framework.ui.view.MainActivity with mDestroyed = false
    │    ↓ NavHostController.mLifecycleOwner
    ├─ com.example.app.framework.ui.view.utils.MainNavHostFragment instance
    │    Leaking: NO (Fragment#mFragmentManager is not null)
    │    componentContext instance of dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper,
    │    wrapping activity com.example.app.framework.ui.view.MainActivity with mDestroyed = false
    │    ↓ MainNavHostFragment.mainFragmentFactory
    │                          ~~~~~~~~~~~~~~~~~~~
D/LeakCanary: ├─ com.example.app.framework.ui.view.utils.MainFragmentFactory instance
    │    Leaking: UNKNOWN
    │    Retaining 212 bytes in 7 objects
    │    ↓ MainFragmentFactory.shopAdapter
    │                          ~~~~~~~~~~~
    ├─ com.example.app.framework.ui.adapter.recyclerview.ShopAdapter instance
    │    Leaking: UNKNOWN
    │    Retaining 14461 bytes in 546 objects
    │    ↓ ShopAdapter.clickListener
    │                  ~~~~~~~~~~~~~
    ╰→ com.example.app.framework.ui.view.fragments.shop.ShopFragment instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.example.app.framework.ui.view.fragments.shop.
    ​     ShopFragment received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)
    ​     Retaining 2121 bytes in 79 objects
    ​     key = 71ec5094-8509-47a5-9e0a-070fe642ca8a
    ​     watchDurationMillis = 18366
    ​     retainedDurationMillis = 13365
    ​     componentContext instance of dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper,
    ​     wrapping activity com.example.app.framework.ui.view.MainActivity with mDestroyed = false

好的,我已经设法解决了这个泄漏问题。 泄漏是造成的,因为我已经通过构造函数注入将我的ShopAdapter注入到我的Fragment 通过构造函数注入将某些内容注入fragment ,您必须将依赖项传递给MainFragmentFactory 但正因如此, MainFragmentFactory始终持有对适配器的引用,即使片段被销毁并且不再需要片段(因此, requireView().findViewById<RecyclerView>(R.id.rv_shop).adapter = null甚至不会在这里进行更改)。

为了解决这个问题,不要通过构造函数注入来注入适配器,而是通过字段注入来注入它。

我知道我参加聚会迟到了,但我可以看到您经常使用lifecycleScope.launch并且在内部调用适配器以提交数据。 这意味着此适配器将无法正确垃圾回收。 这可能是内存泄漏的根源。 尝试使用viewLifecycleOwner.lifecycleScope.launch代替。

这是一个已知错误: https : //proandroiddev.com/5-common-mistakes-when-using-architecture-components-403e9899f4cb

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM