Android RecyclerView 列表性能优化实践
Android RecyclerView 列表性能优化实践
列表卡顿通常不是某一个方法过慢,而是布局测量、图片解码、对象创建、数据更新等开销叠加后的结果。优化 RecyclerView 时,应该先定位掉帧发生在哪个阶段,再针对真实瓶颈处理,避免为了“看起来更快”加入难以维护的缓存。
一、从绑定过程开始检查
onBindViewHolder 会在滚动过程中频繁执行,因此它应该只做轻量的视图赋值。日期格式化、富文本解析、复杂排序和数据聚合等工作,最好在数据进入适配器之前完成。
data class QuoteItemUiModel(
val id: String,
val name: String,
val priceText: String,
val changeText: String,
val changeColor: Int
)
class QuoteViewHolder(
private val binding: ItemQuoteBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: QuoteItemUiModel) = with(binding) {
nameText.text = item.name
priceText.text = item.priceText
changeText.text = item.changeText
changeText.setTextColor(item.changeColor)
}
}
把展示字符串和颜色提前计算成界面模型后,绑定函数保持纯粹,也方便进行单元测试。
二、使用差量更新
notifyDataSetChanged 会让整个列表重新绑定,还可能丢失局部动画信息。对于常规业务列表,可以使用 ListAdapter 配合 DiffUtil.ItemCallback。
object QuoteDiffCallback : DiffUtil.ItemCallback<QuoteItemUiModel>() {
override fun areItemsTheSame(
oldItem: QuoteItemUiModel,
newItem: QuoteItemUiModel
): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(
oldItem: QuoteItemUiModel,
newItem: QuoteItemUiModel
): Boolean = oldItem == newItem
}
areItemsTheSame 判断是否为同一业务对象,areContentsTheSame 判断展示内容是否改变。两者语义不能混用,否则可能出现该刷新的行没有刷新,或者所有行仍被重复绑定。
提交列表时还要注意不可变性。不要修改已经交给适配器的原列表,而应创建包含新状态的新列表:
val updated = currentItems.map { item ->
if (item.id == changedId) item.copy(priceText = newPrice) else item
}
adapter.submitList(updated)
三、利用局部刷新
行情、进度等高频变化场景中,一行往往只有少数字段改变。此时可以通过 payload 避免整行重新绑定。
override fun getChangePayload(
oldItem: QuoteItemUiModel,
newItem: QuoteItemUiModel
): Any? {
return if (
oldItem.name == newItem.name &&
oldItem.id == newItem.id &&
oldItem.priceText != newItem.priceText
) {
PriceChanged(newItem.priceText, newItem.changeColor)
} else {
null
}
}
适配器收到 payload 后只更新价格相关控件。只有确实存在高频、局部变化时才值得这样做,普通列表保持完整绑定通常更清晰。
四、降低布局层级和测量成本
列表项布局越深,测量和绘制成本越高。优化时可以关注以下原则:
- 删除只用于包裹、没有布局作用的容器。
- 复用项目已有的扁平化布局组件。
- 文本控件宽高优先使用
wrap_content,通过约束关系限定可用空间。 - 只有设计明确要求固定区域时才使用固定尺寸。
- 不要在绑定阶段反复修改布局参数,避免触发额外测量。
复杂布局不等于一定慢。先使用性能工具确认测量是否为主要耗时,再决定是否重构,能够减少无收益的代码改动。
五、正确处理图片
列表图片应交给项目统一的图片加载组件处理,以复用内存缓存、磁盘缓存、尺寸采样和请求取消能力。加载时应提供与展示区域接近的目标尺寸,避免把远大于控件的原图完整解码到内存。
视图被复用时,旧请求必须能够取消或被新请求覆盖;占位图、失败图和圆角变换也应采用项目统一配置。手动创建位图缓存通常会重复建设,还可能引入内存回收问题。
六、控制预取与缓存
RecyclerView 自带回收池和缓存机制,大多数列表无需修改。嵌套列表或多个相同类型列表之间,可以考虑共享 RecycledViewPool:
private val sharedPool = RecyclerView.RecycledViewPool()
fun setupHorizontalList(list: RecyclerView) {
list.setRecycledViewPool(sharedPool)
list.layoutManager = LinearLayoutManager(
list.context,
RecyclerView.HORIZONTAL,
false
)
}
提高缓存数量会占用更多内存,设置过小则增加创建次数。除非监控数据证明默认策略不合适,否则不要随意调整。
七、处理高频数据更新
实时数据可能在一秒内多次到达,而屏幕刷新频率有限。每次变化都立即提交完整列表,会造成大量差分计算和主线程任务。可以在数据层按业务允许的时间窗口合并变化,只把最新快照交给界面。
合并并不意味着丢失业务数据。持仓计算、成交记录等要求完整性的逻辑仍应逐条处理;只有展示刷新可以在确保含义正确的前提下采样或合并。
八、建立可验证的优化流程
一次可靠的列表优化通常按以下顺序进行:
- 构造可重复的长列表和滚动路径。
- 记录主线程、布局、绘制和图片加载的耗时。
- 找到最主要的瓶颈,只改动一个变量。
- 在相同设备和相同数据下重新测量。
- 同时观察内存占用,避免用内存换流畅后产生新问题。
总结
RecyclerView 优化的核心是减少无效工作:绑定阶段少计算,数据变化做差量更新,局部变化使用局部刷新,图片按展示尺寸加载,布局保持清晰且不过度嵌套。所有优化都应建立在可重复测量上,而不是依赖经验猜测。
- 点赞
- 收藏
- 关注作者
评论(0)