Android ViewHolder 未正确回收并将位图保留在堆上

Android ViewHolder is not properly recycled and keeps bitmaps on the heap

这里发生了几件事:

  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
    }
}

图片清空可以在onViewRecycled中完成,f.e。 Glide.with(上下文).clear(imageView)。 另外,对于 Glide,检查缓存策略并选择您需要的内容将很有用。 作为尺寸优化,当加载的图像可以更小时,ImageView 将非常有用 Glide.with(context).load(image).centerInside()...