Android ViewHolder 未正确回收并将位图保留在堆上
Android ViewHolder is not properly recycled and keeps bitmaps on the heap
这里发生了几件事:
- 我试过使用Coil / Glide,都遇到同样的问题
-> 我怀疑问题不在图像加载本身,而是在视图持有者的回收过程中
- LeakCanary 未报告任何内存泄漏
- 我使用 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()...
这里发生了几件事:
- 我试过使用Coil / Glide,都遇到同样的问题 -> 我怀疑问题不在图像加载本身,而是在视图持有者的回收过程中
- LeakCanary 未报告任何内存泄漏
- 我使用 Lru 缓存通过查找键存储联系人照片 -> 缓存图像由 Coil / Glide 加载,但仍分配在堆上(同一张图像现在在堆上出现两次)
它是这样的:
在这里你可以看到从上到下滚动几次后它是如何快速填充堆的:
这是我的适配器的样子:
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()...