首页 > 解决方案 > Android ViewHolder 未正确回收并将位图保留在堆上

问题描述

这里发生了几件事:

  1. 我尝试使用 Coil / Glide,都遇到同样的问题 -> 我怀疑问题不在于图像加载本身,而在于视图持有者的回收过程
  2. LeakCanary 没有报告任何内存泄漏
  3. 我使用 Lru 缓存通过他们的查找键存储联系人照片-> 缓存图像由 Coil / Glide 加载,但仍分配在堆上(同一个图像现在在堆上出现两次)

这是它的外观:

在此处输入图像描述 如您所见,我有超过 1000 个位图分配在堆上占用了高达 123MB 的空间,即使我的列表只包含大约 80 个项目并且一次应该只显示大约 10 个。

在这里,您可以看到它在从上到下滚动几次后如何快速填充堆:

在此处输入图像描述

这是我的适配器的外观:

class CustomerAdapter(
    private val viewLifecycleOwner: LifecycleOwner,
    private val listener: CustomerClickListener,
    private val context: Context,
    private val prefsShared: PrefsShared,
    private val imageManager: ImageManager,
) : ListAdapter<Customer..., RecyclerView.ViewHolder>() {
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        ...
        CustomerViewHolderPhoto(
            context, <-------------|
            viewLifecycleOwner, <--|--- Are these actually the problem?
            listener, <------------|
            imageManager,
            binding
        )
    }
}
class CustomerViewHolderPhoto(
    private val context: Context,
    private val viewLifecycleOwner: LifecycleOwner,
    private val listener: CustomerAdapter.CustomerClickListener,
    private val imageManager: ImageManager,
    private val binding: ViewHolderCustomerItemPhotoBinding
) : RecyclerView.ViewHolder(binding.root), Releasable {

    fun bind(contact: EntityContactWithCustomerWithPhoneNumbersAndVisits) {
        ...
        itemView.setOnClickListener {
            listener.onCustomerClicked(contact)
        }

        imageManager.loadContactImageIntoImageViewAsync(
            lifecycleOwner = viewLifecycleOwner,
            imageView = binding.imageViewCustomer,
            lookupKey = contact.contact.contactBookLookupKey,
        )
    }
}
class CustomerFragment : Fragment(), CustomerAdapter.CustomerClickListener {
    ...

    private fun setupCustomerAdapter(): CustomerAdapter {
        val viewManager = LinearLayoutManager(activity)

        val viewAdapter = CustomerAdapter(
            viewLifecycleOwner = viewLifecycleOwner,
            listener = this,
            context = requireContext(),
            prefsShared = prefsShared,
            imageManager = imageManager
        )

        binding.recyclerView.apply {
            layoutManager = viewManager
            adapter = viewAdapter
        }

        return viewAdapter
    }
}

标签: androidkotlinandroid-recyclerviewmemory-leaksout-of-memory

解决方案


图像清除可以在 onViewRecycled 中完成,fe Glide.with(context).clear(imageView)。另外,在 Glide 的情况下,检查缓存策略并选择您需要的内容会很有用。并且作为尺寸优化,当加载的图像可以更小时,ImageView 将有助于使用 Glide.with(context).load(image).centerInside()...


推荐阅读