《Android列表卡顿优化实践》

1,634 阅读4分钟

背景:

随着版本迭代,功能增加,「最近会话」列表在低端机滑动列表时开始有卡顿的现象。

而这个列表是用户重度使用的页面,所以进行性能优化、改善卡顿就很重要了

卡顿的原因:

从业务层的角度,卡顿主要原因有:

1、主线程任务过于耗时

2、view树的measure、layout、draw流程过于耗时

3、GC 时 Stop The World。频繁的GC也就频繁的卡顿,也就是 内存抖动。这是较少人关注到的一点。

对于第2点,可以优化布局、减少层级。这是基本技能,就不展开了。

对于第3点,实测我们APP的该列表即使频繁快速滑动 触发bind(),GC的频率也很低。所以也不展开

所以,本次卡顿主要围绕 主线程任务过于耗时 展开优化。

具体执行步骤:

1.有的放矢:

用工具准确定位 「造成卡顿的主因」

2.执行优化:

对卡顿主因进行优化

3.复盘:

对优化前后进行 「记录和对比」

1、用工具准确定位 造成卡顿的主因

1.1: 用什么工具?

我选择AS自带的CPU Profiler

网上的文章很多都提到 TraceView、Systrace,但是你很容易在Android Dev官网发现,Android已经不推荐这些旧有的方式了(TraceView废弃、Perfetto替代Systrace)。

而对比Perfetto和Profiler,我选择了功能强大、实时性更强的Profiler(其实profiler也是利用了其他已有的工具如System Trace,缝合进as里)。

1.2:查看卡顿情况

来到AS最新版本 Clipmunk,在CPU Profiler下,首先选择「System Trace Recording」,

Callstack Sample Recording.jpg

点Record按钮,然后快速滑动屏幕。Record完成后,查看一下卡顿情况

b0ef4159e8824244b126accc60ef010b~tplv-k3u1fbpfcp-zoom-in-crop-mark-4536-0-0-0.image.png

红色的就是卡顿帧。可以看到,在低端机上,卡顿还是很严重的。(在稍微好一点的机子上其实基本没有Janky帧)

放大局部区域:

283ea0dafecf4ece989de9c1f037915a~tplv-k3u1fbpfcp-zoom-in-crop-mark-4536-0-0-0.image.jpg

可以看到,耗时主要花在 View#draw()、RV#onBindView()。而具体是draw()的哪里呢?「System trace」帮不了我们,正如其名,它只能检测System侧的调用

接下来需要用到Profiler 的「Method and function traces」功能

对于Clipmunk版本,几个选项我都使用了一下,觉得还是标记为(legacy)的选项比较趁手:

Callstack Sample Recording.jpg

重复以上步骤:Record后快速滑动列表。完成后查看结果(在结果页中 点选「main Thread」、Flame Chart火焰图)

5f401e74f12e43d299936f61e41580ba~tplv-k3u1fbpfcp-zoom-in-crop-mark-4536-0-0-0.image.jpg

方法耗时情况一目了然。

以我这里为例,耗时大的****method,主要有截图中的标注为 3 4 等等的方法(getChatListTime()、isMute())

72891455d69f44cc99ad276d20599860~tplv-k3u1fbpfcp-zoom-in-crop-mark-4536-0-0-0.image.jpg

2、执行优化

具体怎样优化,要以各位的应用的具体情况而定。

图中的3 :

主要耗在 bind()下面的 getChatListTime()的方法,具体是对时间戳进行format处理。

原来的处理算法,的确是有点效率问题。所以这个问题的解决方案是 优化算法效率

实测优化后的耗时仅仅是原来耗时的 1/7

图中的4 :

主要是 bind()下面的 调用第三方SDK的一个叫isMute()的方法。

这里大可不必每次onBind()都向第三方SDK查询isMute()。我们可以把查询的结果缓存下来。当然,要做好Mute设置改变后的通知和刷新机制。 所以这个问题的解决方案是 设置缓存

private Boolean 1sMute.jpg

改善了上面两个业务相关的耗时方法,现在压力来到了Glide****相关的图片加载方法。

12f6d5c30d624fed8733d44bd9598cdb~tplv-k3u1fbpfcp-zoom-in-crop-mark-4536-0-0-0.image.jpg

这里我的对策是:列表滑动时不加载图片只展示占位图,列表停顿时才加载图片。姑且认为是 业务降级 的策略吧

把上面的主要耗时方法都优化后,卡顿情况改善了一个档次

1c3231fd2afd439ca9a3abef368b2e7e~tplv-k3u1fbpfcp-zoom-in-crop-mark-4536-0-0-0.image.png

对应地,main Thread 的火焰图,onBind()耗时所占的百分比,已经比较低了

7bfb5f625e974722bea6d21b48894a59~tplv-k3u1fbpfcp-zoom-in-crop-mark-4536-0-0-0.image.jpg

最后的最后,优化itemView布局,减少视图嵌套层次。这个收益也很明显

381efbec35f647b0a0f8d913b1bf62f3~tplv-k3u1fbpfcp-zoom-in-crop-mark-4536-0-0-0.image.png

3.复盘:优化前后进行对比

优化前卡顿帧:

b0ef4159e8824244b126accc60ef010b~tplv-k3u1fbpfcp-zoom-in-crop-mark-4536-0-0-0.image.png

优化后卡顿帧:

381efbec35f647b0a0f8d913b1bf62f3~tplv-k3u1fbpfcp-zoom-in-crop-mark-4536-0-0-0.image.png

效果很明显,优化后卡顿帧只是偶尔出现(并且大部分卡顿是在同时处理 Touch Event时的帧才出现)。