在Android的开发中,滚动列表是一个出镜率非常高的组件。这其中RecyclerView是当然不让的明星。从Native到weex,RecyclerView有着非常广泛的使用。正因如此,我们持续不断地针对RecyclerView页面进行优化。在最近一次调研RecyclerView优化的过程中,偶然看到google在最新版版本的RecyclerView中增加了Prefetch功能。Prefetch的功能到底做了哪些事情,是不是真的能提升页面性能,使用的成本有多高?
针对这些问题,本文将分三步来做一个简单的探究:
- (1)功能怎么用
- (2)效果怎么样
- (3)原理是什么
1. Prefetch功能的使用
在研究Prefetch功能的实际效果之前,首先需要能正常使用Prefetch功能。
- 第一步 :升级 Support Library的版本到 25.3.1。
google官方在 Support Library v25 版本中,为RecyclerView增加了Prefetch。
并且在 v25.1.0 以及25.3.0版本中进行了完善。在最新的稳定版本25.3.1中已经基本稳定。
如果需要使用建议升级到25.3.1版本。详细可见support-library change log注意: android support v4 包从24.2.0版本开始拆分成了多个更小的模块。
包括:support-compat,support-core-utils,support-core-ui,support-media-compat以及
support-fragment。如果出现使用compile group: 'com.android.support', name: 'support-v4', version: '25.3.1', ext: 'jar'
出现了类找不到的情况,需要按照需要依赖上述子包。
- 第二步:实现LayoutManager
- 如果你使用的是官方的LayoutManager,那么直接可以获取Prefetch的功能,无需任何其他的定制。
- 但是如果你使用的是自定义的LayoutManager,需要重写LayoutManager.collectAdjacentPrefetchPositions()
方法。如果嵌套RecyclerView使用需要复写setInitialPrefetchItemCount
详细信息可以参见具体的API文档(点击直接跳转) - 第三步:设置打开Prefetch
默认情况下Prefetch的功能是打开的。当然也可以手动选择关闭:
LayoutManager.setItemPrefetchEnabled(false);
所以确认一下不要手动关闭预取功能
在完成上述三个步骤以后,可以像往常一样使用RecyclerView,并且自带了prefetch功能。
2.Prefetch实际效果
RecyclerView的Prefetch功能正常运转以后。那么这个功能到底能带来多少性能的收益是现在需要确认的。分几个部分来看:
demo
- demo内容:
使用Android官方提供的StaggeredGridLayoutManager实现了一个瀑布流。
瀑布流中文字的数量和图片的大小都是可以变的。展示如下:
衡量标准&方法:
- GPU呈现模式分析 打开的GPU渲染条形图。图中的绿线是流畅度的分割线。超过绿线的帧都是渲染超时的。比较的核心指标是滑动过程中,超出绿线frame占比。
(注:流畅的标准是1s 60帧 一帧的渲染时间大约是16ms)
- 测试设备小米5s
- 数据绑定复杂度模拟:
为了检测预取功能在不同的页面渲染复杂度情况下的实际效果。在RecyclerView数据绑定函数:onBindViewHolder函数中,使用Thread.sleep(time);
来模拟页面渲染的复杂度。复杂度的大小,通过time时间的长短来体现。时间越长,复杂度越高。
原始demo流畅度对比
打开prefetch | 关闭prefetch |
---|---|
在原始demo中,由于复杂度有限,无论是打开或者关闭prefetch功能,滑动过程中无明显差异。
延时8ms 流畅度对比
打开prefetch | 关闭prefetch |
---|---|
通过延时8ms,整个数据绑定的复杂度急剧上升。在关闭prefetch功能的情况下,在16ms 内无法完成的渲染的帧数明显增多。相比之下打开prefetch以后,页面依然非常流畅。
延时12ms 流畅度对比
打开prefetch | 关闭prefetch |
---|---|
通过延时12ms在关闭prefetch功能的情况下,在16ms 内无法完成的渲染的帧数进一步增多,页面卡顿愈加明显。但是打开prefetch以后,页面依然非常流畅。
延时17ms 流畅度对比
打开prefetch | 关闭prefetch |
---|---|
通过延时17ms,在16ms时间内完成渲染已经无法做到。滑动过程中,滑动卡顿可以明显被感知。在关闭prefetch功能的情况下,在16ms内无法完成的渲染的帧数进一步增多,并且延时也更长。打开prefetch以后,同样不可避免的会出现16ms无法渲染帧,但是页面对比下还是更加流畅。
结论:基于上述实验,可以得出以下几个结论:
- 使用prefetch以后,recyclerView的流畅度对页面复杂度的敏感性降低。
- 在高复杂度(recyclerView的子项view的创建和绑定更加耗时)情况下,prefetch功能确实能显著提高页面流畅度。
3.Prefetch原理:
android 5.0以后,android系统为了提高UI渲染的效率引入了RenderThread。通过这样的设计,将主线程从UI
渲染的繁重工作中解脱出来。在UI渲染过程中,主线程可以更加专注于跟用户进行交互。这样可以大大提高页面流畅度。
通过Systrace工具跟踪一个页面,获取页面渲染详细数据。
注意:如果使用DDMS来抓取。一定要记得在Enable Application Traces from 中选择APP所在的进程。否则你可能无法观察到Prefetch的渲染。如下图所示:
一个常规页面的渲染流程如下:
通过上面的展示,UI线程准备页面数据并且在ready以后交由Render线程渲染。帧与帧之间通过帧同步信号sync来同步。如果页面渲染无法在规定时间内完成,就会出现丢帧情况渲染的时序会变成如下情况:
不难发现在正常渲染过程中,有一个非常的明显的特点:在UI线程将页面数据交由Render
线程渲染以后,会出现大量的空闲时间。如下图所示:
这些空闲等待时间就被浪费了。Prefetch的核心思想就是利用这部分空闲时间来预先处理item的创建和数据绑定[2]。
对比一下使用了Prefetch以后的渲染时序图如下:
时空上的复用,会大大提高页面渲染的效率,提高页面流畅度。
4. 收益
在实际开发的过程中,任何的升级都需要考量投入产出的比例。Prefetch优化影响的页面包括:
- (1)Native页面中使用RecyclerView的页面
- (2)weex页面中使用list的页面
注意: 只有Android 5.0以后版本的手机才能获得这个优化。
通过简单的升级(可能需要解决一些兼容问题),就可以使几乎所有的RecyclerView页面性能从中受益。并且在sourceCode层面不需要做额外的定制(自定义和嵌套除外)。投入产出比还是非常可观的,所以
建议大家尽快升级吧!
5. 引用
- [1]Android应用程序UI硬件加速渲染的动画执行过程分析
- [2]RecyclerView Prefetch 注意翻墙
- [3]Profile GPU Rendering Walkthrou
- [4]Android Support Library
- [5]Systrace