优化页面渲染速度的一个方向是简化布局和减少嵌套,ConstraintLayout
的特性很适合处理这方面的优化,一直以来我也是尽可能的在各种场景下使用ConstraintLayout
,保证Fragment
页面或者 RecyclerView
的ViewHolder
里的itemView
中只有一个布局,但是最近在适配新的开发板
设备过程中,个别页面打开速度出现比较严重的卡顿并在logcat
里打印了ANR Warning
日志,从而让我定位到了卡顿的元凶是ConstraintLayout
的onLayout
和onMeasure
方法,这篇文章就是记录该问题出现和解决的过程。
背景
我目前所开发的项目在某种程度上来说也算得上是大屏TV
类的Android
开发,特点就是一些特殊页面控件元素众多,为了防止这些View
元素出现布局嵌套整个页面就一个ConstraintLayout
,开发完后的感受就是打开页面时会在视觉上有滞后感,也就是UI
卡顿。不过这个问题当时并没有成为严重到APP不能使用的地步,前段时间也专门抽出一些时间做专项优化治理,优化手段包括代码代替xml
创建布局、优化列表数据加载、View
缓存复用和异步加载等等,直接体现就是Fragment
的各个生命周期执行时间得到明显的缩短。
1 |
|
卡顿问题有所缓解却没有得到根治,直到最近在适配Android 5.1
系统的新设备,卡顿问题在该设备上被放大且伴有logcat
的ANR Warning
日志,这让我发现了新的优化方向,让我比较意外的是ConstraintLayout
这一我在任何开发场景都比较推崇使用的布局在一些场景下也有局限性,物极必反的道理也在其中有很好的体现。
问题分析及解决
目前出现ANR Warning
日志的页面主要有两例,下面就来介绍详细情况跟相关的日志。
1. onMeasure
方法卡顿
该场景下发生卡顿的原因是ConstraintLayout
下的直接子View
众多,减少嵌套这种优化在我看来最好是没有嵌套,页面中所有的元素都在一个Layout
中,当页面展示动态数据时就会有以下日志打印:
1 | [ANR Warning]onMeasure time too long, this =androidx.constraintlayout.widget.ConstraintLayout{5e6add4 V.E..... ......I. 0,0-0,0}time =1437 ms |
原理大概是ConstraintLayout
下的TextView
的text
变动或者一些View
的visibility
属性变动会导致ConstraintLayout
重新测量,导致onMeasure
方法方法多次执行从而导致卡顿,解决方法也比较简单,就是数据变动涉及到的控件使用一个父布局做一层嵌套,这样个别控件的宽高变动不会导致整个布局重新测量,从而避免onMeasure
方法的卡顿。ConstraintLayout
的出现是为了减少嵌套而不是避免嵌套,在适当的场景下增加一层嵌套反而能提高页面加载的效率。
当然这种增加嵌套的方式比较适合当前我这种childView
众多的卡顿的场景,也有通过自定义ConstraintLayout
中重写onMeasure
方法来减少不必要的测量优化方式,还是要具体问题具体分析。
2. onLayout
方法卡顿
这个页面的场景是应用的主页,页面采用TabLayout
+ViewPager
+Fragments
的结构,是一种比较典型的TV
类布局结构,原型图大致如下:
子Fragment
里使用RecyclerView
展示页面数据,部分页面加载中状态时使用Fake
数据填充RecyclerView
来达到预览的效果,由于ViewPager
预加载的处理会导致页面打开时会有三个Fragment
同时加载,这样的场景下ViewPager
的onLayout
方法就耗时比较严重,从而导致onLayout
方法卡顿。当然也有一部分原因是我在ViewHolder
里的itemView
里使用了ConstraintLayout
来做布局。
1 | [ANR Warning]onLayout time too long, this =androidx.viewpager.widget.ViewPager{30c079ac VFED.... ......ID 0,100-1024,600 #7f090364 app:id/vp_container}time =441 ms |
这个场景下的优化思路就比较明显了,主因是ViewPager
下三个Fragment
同时加载造成的同一时间需要onLayout
的View
太多,次因则实在ViewHolder
中使用了ConstraintLayout
,Fragment
层面则是采用非当前显示的Fragment
采用延迟加载的策略,ViewHolder
层面则是替换掉ConstraintLayout
的使用,使用FrameLayout
、LinearLayout
或RelativeLayout
代替。
之所以确定在
ViewHolder
中使用ConstraintLayout
会造成onLayout
耗时严重的问题,是因为在切换到仅使用一个图片来表示加载中的状态的页面时也会打印上述ANR Warning
日志,替换成其它普通布局则没有问题。
总结
这次卡顿优化的过程再次验证了我在Android高手开发课
中看到的关于性能优化的总结的一句话的含金量,就是性能优化的终点就是监控,性能的瓶颈在于如何被发现,就像我们日常解决Bug
一样,只要有Bug
的重现路径,解决Bug
反而是其中比较简单的一环,对于我而言,在性能优化的道路上还有很长的一段路要走。