一、数据拉取与处理
1.1 数据处理
大家都知道,数据从网络和本地磁盘加载到内存,为避免主线程阻塞会在io线程上进行。但绑定视图数据时,重量级转换操作也会导致列表滚动卡顿。如果能够预计算,如:字符串替换、拼接、类转换,数据填充到 Adapter 前和加载过程一并在 computation 线程完成转换。
1.2 数据缓存
ViewHolder 共享数据从 Adapter 获取公共变量,避免保存多个副本或要求实时计算。
1.3 局部更新
局部更新可减少刷新所需时间,推荐使用 DiffUtil 计算数据集。
1
2
3
4
fun addItem(reply: Reply) {
list.add(0, reply)
notifyItemInserted(0)
}
二、视图优化
2.1 填充次数
RecyclerView 三级缓存目的是复用已填充视图。当缓存数量不足填满屏幕而频繁创建,滑出屏幕后又超过缓存阈值被销毁,多发生在高度很小的视图。这既占用处理器时间片,同时又产生临时对象。
根据经验来看,RecyclerView 默认缓存阈值偏小,不同类型需要缓存最大数量也不同。最好的方式,是根据屏幕和视图尺寸动态计算分类缓存所需数量。
2.2 绘制优化
减少过度绘制同样适用于 RecyclerView 视图布局,提高滑动帧率。此外,部分 item 可能通过自定义 View 绘制视图,因此也需遵守其性能优化规范。
2.3 布局填充时间
LayoutInflater 实例化视图时不仅需要遍历xml节点,而且视图用反射实例化,导致复杂视图耗时较长,列表快速滚动容易卡顿。降低布局复杂度、增加布局缓存数量都能有效缓解。虽然 ConstraintLayout 具备去除布局层次的能力,不过很多开发者发现和 RecyclerView 使用有严重问题。
原生视图组装、Anko、Litho、JetPack compose 原理都类似,没有xml遍历和类反射而提升性能。
不过上述方案各自缺点也明显:原生视图编写代码量大;Anko 配合style使用要自定义方法;Litho 由于技术实现令视图灵活性较低;JetPack compose 还处于实验性阶段,功能不够完善。按照现在的发展形势来看,个人推荐 Anko 和 JetPack compose。
原生视图组装:
1
2
3
4
5
6
7
8
9
10
11
12
13
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val params = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
params.gravity = Gravity.CENTER
val button = Button(this)
button.text = "Start"
button.isAllCaps = false
button.setOnClickListener { startActivity<MessagesActivity>() }
addContentView(button, params)
}
Anko 代码示例 phantomVK/MessageKit - MessageHolder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class TextMessageLayout : AnkoComponent<ViewGroup> {
override fun createView(ui: AnkoContext<ViewGroup>) = with(ui) {
frameLayout {
lparams(WRAP_CONTENT, WRAP_CONTENT)
textView {
id = R.id.text
autoLinkMask = Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS
gravity = Gravity.CENTER_VERTICAL
includeFontPadding = false
minimumHeight = dip(40)
padding = dip(10)
textColor = Color.parseColor("#222222")
setTextIsSelectable(false)
textSize = 16f //sp
}
}
}
}
Jetpack compose 官方示例 Jetpack Compose Basics
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Preview
@Composable
fun NewsStory() {
val image = +imageResource(R.drawable.header)
MaterialTheme {
Column(
modifier = Spacing(16.dp)
) {
Container(modifier = Height(180.dp) wraps Expanded) {
Clip(shape = RoundedCornerShape(8.dp)) {
DrawImage(image)
}
}
HeightSpacer(16.dp)
Text("A day in Shark Fin Cove")
Text("Davenport, California")
Text("December 2018")
}
}
}
关于 Litho 的使用美团有深入研究,参考 基本功 - Litho的使用及原理剖析 和 Litho在美团动态化方案MTFlexbox中的实践。
2.4 对象生成
当 RecyclerView 视图分类较多时,缓存元素总数也会很多,加上复杂视图 ViewHolder 很多数据成员占用很多内存。用测量工具对比列表展示前后内存占用,可以大概确定用量。结构样式相似的视图可用 View.visibility 合并类别。
三、参数配置
RecyclerView 自有参数也能提升渲染性能。
3.1 setHasFixedSize
描述 RecyclerView 是否根据items总宽高重新计算大小。多数情况 RecyclerView 宽高是 MATCH_PARENT,设置为true减少重新计算大小次数。父布局大小改变而导致 RecyclerView 调整不受此参数限制。
1
recyclerView.setHasFixedSize(true)
3.2 mCachedViews
RecyclerView 离屏缓存 mCachedViews 默认为2,即视图移出屏幕后,放到共享缓存池前缓存2个元素,目的优化慢速向前、向后滑动的抖动。
1
recyclerView.setItemViewCacheSize(4)
按照个人经验判断这个值设置超过4效果不明显。
3.3 setHasStableIds
指定参数为true,避免增删items时引起无关的视图刷新。
1
adapter.setHasStableIds(true)
当然还需要重写方法获取id计算结果。
1
2
3
override fun getItemId(position: Int): Long {
return items[position].id.hashCode().toLong()
}
四、参考链接
- RecyclerView Scrolling Performance
- How to optimize Recyclerview in Android
- RecyclerView item optimizations
- Improve RecyclerView Performance
- 基本功 - Litho的使用及原理剖析
- Litho在美团动态化方案MTFlexbox中的实践
- jetpack compose - Android’s modern toolkit for building native UI