上篇介绍了android界面渲染主要是Display的过程,只要在一个时间段也就是16ms中,CPU和GPU不能正常处理完数据就会产生卡顿.
而
- CPU(中央处理器) :多缓存多分支,适用于复杂的逻辑运算,主要负责Measure,Layout,Record,Execute的计算操作
- GPU(图像处理器):众核少缓存,适用于结构单一的数据处理,主要负责Rasterization(栅格化)操作
实际上最后还是回到Measure,Layout,和draw上.也就是说上述三个步骤中的某一个步骤出现了耗时较长的操作,就容易导致界面卡顿。一般来说多发生在draw上。
过度绘制(Overdraw)
什么是过度绘制?
Overdraw(过度绘制)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次重叠的UI结构里面,如果不可见的UI也在做绘制的操作,会导致某些像素区域被绘制了多次。这样就会浪费大量的CPU以及GPU资源。
如何检测过度绘制
开发者选项->调试GPU过度绘制->显示过度绘制区域�
蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域
如何优化过度绘制
移除布局中多余的背景
layout层级扁平化,或者该用约束布局(ConstraintLayout)
减少透明度的使用
移除Window默认的Background
通常,我们使用的theme
都会包含了一个windowBackground
,比如theme
的如下:
- @color/background_material_light
然后通常我们自己的layout
下,会另外加一层背景,这就会引起过度绘制,
一般的解决方式把theme
下的Background
移除
或者在Activity中的onCreate()下添加
getWindow().setBackgroundDrawable(null);
去除子控件的背景
在开发中,经常为了一些视觉效果,需要给子控件附加背景.
如果按照过度绘制的思想来看这个问题的话,那这里也会村子啊过度绘制的情况,最合理的设想是如果子view有背景,并且跟父view背景不同,那么应该移除父view的背景,保留子view的背景,这样就能避免过度绘制了。
不过如果真的严格按照这个逻辑来写代码的话,会导致多写很多代码.况且以目前手机的性能来讲,x1级别的过度绘制不太会影响到性能。
将layout层级扁平化
使用Layout Inspector去查看layout的层次结构
老版本Android SDK提供的是一个Hierarchy Viewer
工具,可以用来分析布局深度,
但是在Android studio3.1之后,换成了Layout Inspector
,入口在Tools-->Layout Inspector
使用嵌套少的布局
相同层级下,Fragment的效率最高,其次是LinearLayout
LinearLayout对比RelativeLayout
-
LinearLayout
在没有设置weight属性的时候,只会对子视图进行一次measure
,而RelativeLayout
会在水平方向和竖直方向分别进行一次measure
- 另外,系统的
View
的measure
方法里对绘制过程做了一个优化,如果我们或者我们的子View没有要求强制刷新,而父View给子View的传入值也没有变化(也就是说子View的位置没变化),就不会做无谓的measure
。但是RelativeLayout
会做两次measur
e,而在做横向的测量时,纵向的测量结果尚未完成,只好暂时使用myHeight
传入子View
系统,假如子View
的Height
不等于(设置了margin
)myHeight
的高度,那么measure中上面代码所做得优化将不起作用,所以尽量使用padding代替margin
RelativeLayout和LinearLayout性能比较
使用抽象布局 include、merge 、ViewStub
include、merge 、ViewStub这三个布局都是官方提供的用于布局优化的
布局重用 include
标签复用,比起性能上的优化,更大的作用我感觉在xml文件的优化上
减少布局层级 merge
如果xml的根部局是
FrameLayout
,直接替换成merge
就能减少一个布局深度。如果你的主布局文件是垂直布局,引入了一个垂直布局的
include
,这个时候就可以将include
的根布局替换成merge
延迟加载 ViewStub
ViewStub
是一个不可见的,大小为0的View
,能为xml加载减小不少压力。常用于 进度条、显示错误消息等不需要第一时间显示的视图,inflate()
之后会被对应的layout所代替
注:ViewStub目前有个缺陷就是还不支持
使用lint来优化布局的层次结构
Analyze->Inspect code
布局性能方面的信息位于Android
> Lint
> Performance
下,我们可以点开它来看下一些优化建议。
下面是lint的一些优化技巧:
- 使用复合图片
如果一个线性布局中包含一个ImageView
和一个TextView
,可以使用复合图片来替换掉 - 合并根节点
如果一个FrameLayout
是整个布局的根节点,并且也没有提供背景、留白等等,那么可以使用
标签来替换掉,因为DecorView
本身就是一个FrameLayout
。 - 移除布局中无用的叶子
布局是一个树形的结构,如果一个布局没有子View
或者背景,那么可以把它移除掉(这布局本身就不可见了)。 - 移除无用的父布局
如果一个布局没有兄弟,也不是ScrollView
或者根View
,并且也没有背景,那么可以把这个父布局移除掉,然后把它的子view
移到它的父布局下。 - 避免过深的层次结构
过多的布局嵌套不利于性能,可以使用更扁平化的布局,如RelativeLayout
、GridLayout
、ConstraintLayout
等布局来提高性能。布局默认的最大深度为10
减少透明度的使用
对于不透明的view
,只需要渲染一次即可把它显示出来。但是如果这个view
设置了alpha
值,则至少需要渲染两次。这是因为使用了alpha
的view
需要先知道混合view
的下一层元素是什么,然后再结合上层的view
进行Blend混色处理。透明动画、淡入淡出和阴影等效果都涉及到某种透明度,这就会造成了过度绘制。可以通过减少渲染这些透明对象来改善过度绘制。比如:在TextView
上设置带透明度alpha
值的黑色文本可以实现灰色的效果。但是,直接通过设置灰色的话能够获得更好的性能。
减少自定义View的过度绘制,使用clipRect()和QuickReject()
-
clipRect():可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视,可以有效的节约CPU与GPU的资源
canvas.clipRect(0, 0,200,200); //指定(0,0)到(200,200)的矩形区域为绘制区域
QuickReject():判断画布是否与你指定的矩形相交,借此来决定是否需要绘制该画布
boolean skipDraw=canvas.quickReject(new RectF(), Canvas.EdgeType.AA);
onDraw()中不要创建新的局部变量以及不要做耗时操作
onDraw()
中不要创建新的局部变量,因为onDraw()
方法可能会被频繁调用,大量的临时对象会导致内存抖动,会造成频繁的GC,从而使UI线程被频繁阻塞,导致画面卡顿。这种问题一般android studio的lint静态检查都会报警告,平时多注意些android studio标黄的警告,有助于性能调优
尝试使用ConstraintLayout
-
较高的性能优势
比起LinearLayout 、RelativeLayout等,ConstraintLayout可以用更少的布局层级来达到相同的效果
-
完美的屏幕适配
ConstraintLayout的大小、距离都可以使用比例来设置,所以其适配性更好。
当然不是任何时候都推荐使用ConstraintLayout
的,在简单的布局下还是建议使用LinearLayout
、RelativeLayout
,因为对于相同层级的布局下,ConstraintLayout
的measure,layout,draw等过程会显得更加重.
参考文档
Android性能优化之渲染篇
那些 Android 程序员必会的视图优化策略
Android 约束布局 ConstraintLayout图形化操作
约束布局ConstraintLayout用法全解析
官方文档