RecyclerView 性能优化 | 把加载表项耗时减半 (一)

本文分享了一次针对Android RecyclerView的性能优化过程,通过预优化、动态构建布局和替换ViewGroup来减少measure + layout的耗时。经过优化,将加载耗时从370ms降低到288ms,实现了显著的性能提升。

构建 Android App 界面时,RecyclerView 出场率很高。它的加载性能影响着用户体检。本篇分享一次完整的 RecyclerView 性能优化过程:从用工具定位问题,再不断尝试各种优化方案,最终达成 50% 的性能优化。

这次性能调优的界面如下:

界面用列表的形式,展示了一个主播排行榜。

预优化,先量化

这个排行榜嵌套在一个 ViewPager 中。最初发现性能问题是因为滑动到该界面时,ViewPager 指示器的平移动画卡了一下,掉帧了。

虽然卡顿是肉眼可见的,但若不能量化卡顿,就无法量化优化程度。

第一个想到的工具是GPU 呈现模式分析。开启它的路径如下:打开手机设置 — 开发者选项 — GPU 呈现模式分析 — 在屏幕上显示为条形图:

开启后,绘制性能就会图形化展示如下:

果然有很大的性能问题,柱子都快冲出屏幕了。

虽然图形化很直观,但量化地还不够细致,绘制耗时最好能精确到毫秒。所以转战到另一种方式“在 adb shell dumpsys gfxinfo 中”。选中后,打开排行榜界面,然后输入命令adb shell dumpsys gfxinfo <包名>,最近 n 针的渲染时长就会罗列如下:

        Draw    Prepare Process Execute
        50.00   0.23    6.82    1.28
        50.00   0.26    1.49    1.13
        7.01    0.24    1.58    0.76
        6.41    0.52    7.42    1.34
        13.13   0.18    2.01    0.76
        4.38    0.15    1.72    0.39
        4.37    0.15    1.13    0.33
        4.36    0.15    1.23    0.38
        4.34    0.35    1.15    0.31
        4.36    0.15    1.16    8.42
        4.32    0.14    1.11    0.31
        4.32    0.15    1.10    0.32
复制代码

每一行代表一帧渲染中各个阶段的耗时。

用另一个命令还可以得到更加精确的数据:adb shell dumpsys gfxinfo <包名> framestats,该命令会从应用生成的最近 120 个帧中输出带有纳秒时间戳的帧时间信息:

Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,
1,299873182486990,299873215820322,9223372036854775807,0,299873227699771,299873227750761,299873228134563,299873242278000,299873243236959,299873243432011,299873243482063,299873244517375,299873245505396,62000,670000,
0,299873232346485,299873249013151,9223372036854775807,0,299873253133625,299873253191177,299873253443990,299873418812375,299873433404406,299873433753313,299873434022167,299873435099667,299873435955448,71000,453000,
0,299873448312229,299873448312229,9223372036854775807,0,299873448760344,299873448798573,299873449290656,299873449438469,299873449500969,299873449733261,299873449909979,299873450770344,299873451478625,65000,264000,
0,299873464924749,299873464924749,9223372036854775807,0,299873465493625,299873465550292,299873466377896,299873466594511,299873466643417,299873466932115,299873468812427,299873475972792,299873476852011,145000,198000,
0,299873481537390,299873481537390,9223372036854775807,0,299873481932896,299873481972688,299873482590188,299873482741333,299873482772688,299873483047375,299873483263886,299873483856958,299873484308886,72000,96000,
0,299873498151975,299873498151975,9223372036854775807,0,299873498582427,299873498633417,299873499218833,299873499442271,299873499483781,299873499850761,299873500327427,299873501493886,299873502170708,126000,186000,
0,299873514764506,299873514764506,9223372036854775807,0,299873515260031,299873515314667,299873515966646,299873516205656,299873516254667,299873516538052,299873516896229,299873518042063,299873518653990,141000,149000,
0,299873531376920,299873531376920,9223372036854775807,0,299873531891646,299873531951906,299873532798261,299873533022792,299873533072219,299873533390292,299873533748886,299873534813729,299873535444771,118000,160000,
0,299873547989162,299873547989162,9223372036854775807,0,299873548571750,299873548638990,299873549306594,299873549534042,299873549590969,299873549916958,299873550342167,299873551852063,299873552613886,128000,200000,
0,299873564601513,299873564601513,9223372036854775807,0,299873565070500,299873565126281,299873565700031,299873565924302,299873565977479,299873566281073,299873566635865,299873567892375,299873568616386,143000,167000,
0,299873581213965,299873581213965,9223372036854775807,0,299873581577948,299873581621073,299873583047271,299873583253833,299873583293729,299873583680031,299873584854875,299873592375136,299873593527688,222000,308000,
复制代码

原生输出信息没有可读性,但它们遵守 csv 格式,复制粘贴到 wps 表格中,选中 数据 — 分列,用“逗号”分割:

数据就以表格的形式展示:

每一行表示一帧绘制的时间信息,一共有 16 列,每一列表示一个关键节点的时间戳,比如PerformTraversalsStart表示绘制遍历的开始时间点,DrawStart表示onDraw()开始的时间点,前者减去后者表示measure + layout的耗时。

利用表格的求差功能可以计算出一排表征性能的耗时。

虽然得到了量化数据,但是这么折腾着实有点辛苦。

一顿搜索之后,终于找到了下面这个高效的方法:

class TestActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        window?.addOnFrameMetricsAvailableListener(Window.OnFrameMetricsAvailableListener { window, frameMetrics, dropCountSinceLastInvocation ->
           Log.v("test","measure + layout=${frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION)/1000000}, " +
                   "  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值