Android RecyclerView 列表性能优化实践

举报
yd_223002268 发表于 2026/09/07 08:53:12 2026/09/07
【摘要】 Android RecyclerView 列表性能优化实践列表卡顿通常不是某一个方法过慢,而是布局测量、图片解码、对象创建、数据更新等开销叠加后的结果。优化 RecyclerView 时,应该先定位掉帧发生在哪个阶段,再针对真实瓶颈处理,避免为了“看起来更快”加入难以维护的缓存。 一、从绑定过程开始检查onBindViewHolder 会在滚动过程中频繁执行,因此它应该只做轻量的视图赋值。...

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

提高缓存数量会占用更多内存,设置过小则增加创建次数。除非监控数据证明默认策略不合适,否则不要随意调整。

七、处理高频数据更新

实时数据可能在一秒内多次到达,而屏幕刷新频率有限。每次变化都立即提交完整列表,会造成大量差分计算和主线程任务。可以在数据层按业务允许的时间窗口合并变化,只把最新快照交给界面。

合并并不意味着丢失业务数据。持仓计算、成交记录等要求完整性的逻辑仍应逐条处理;只有展示刷新可以在确保含义正确的前提下采样或合并。

八、建立可验证的优化流程

一次可靠的列表优化通常按以下顺序进行:

  1. 构造可重复的长列表和滚动路径。
  2. 记录主线程、布局、绘制和图片加载的耗时。
  3. 找到最主要的瓶颈,只改动一个变量。
  4. 在相同设备和相同数据下重新测量。
  5. 同时观察内存占用,避免用内存换流畅后产生新问题。

总结

RecyclerView 优化的核心是减少无效工作:绑定阶段少计算,数据变化做差量更新,局部变化使用局部刷新,图片按展示尺寸加载,布局保持清晰且不过度嵌套。所有优化都应建立在可重复测量上,而不是依赖经验猜测。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。