神奇的16ms
Android 系统每隔 16ms 发出 VSYNC 信号触发对UI进行渲染,那么就要求每一帧都要在 16ms 内绘制完成(包括发送给 GPU 和 CPU 绘制到缓冲区的命令),这样就能够达到流畅的画面所需要的60fps。
如果你的某个操作花费时间是24ms,系统在得到 VSYNC 信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在 32ms 内看到的会是同一帧画面。
有很多原因可以导致丢帧(卡顿),这里列举一些常见的:
- layout 太过复杂,层次过多
- UI 上有层叠太多的绘制单元,过度绘制
- CPU 或者 GPU 负载过重
- 动画执行的次数过多
- 频繁 GC,主要是内存抖动
- UI 线程执行耗时操作
- 等等
layout 太过复杂,层次过多
layout 布局是一棵树,树根是 window 的 decorView,套嵌的子 view 越深,树就越复杂,渲染就越费时间。每个 View 都会经过 measure、layout 和 draw 三个流程,都是从树根开始,那么选父布局的时候就要考虑渲染的性能问题:这里分析一下常用的布局控件 LinearLayout 和 RelativeLayout:
LinearLayout
LinearLayout 在 measure 的时候,在横向或者纵向会去测量子 View 的宽度或高度,且只会测量一次,但是当设置 layout_weight 属性的时候会去测量两次才能获得精确的展示尺寸。
RelativeLayout
RelativeLayout 在 measure 的时候会在横向和纵向各测量一次。
简析
如果带有 weight 属性的 LinearLayout 或者 RelativeLayout 被套嵌使用,measure 所费时间可能会呈指数级增长(两个套嵌的叶子 view 会有四次 measure,三个套嵌的叶子 view 会有8次的 measure)。为了缩短这个时间,保持树形结构尽量扁平(深度低),而且尽量要移除所有不需要渲染的 view。
优化
- 避免复杂的 View 层级
- 避免 layout 顶层使用 RelativeLayout
- 布局层次相同的情况下,使用 LinearLayout
- 复杂布局建议采用 ConstraintLayout 或 RelativeLayout 而不是多层次的 LinearLayout
- <include/> 标签复用
- <merge/> 标签减少嵌套
- 尽量避免 layout_weight
- 视图按需加载或者使用 ViewStub
注:不会查看布局层次及时间的请查看上一篇UI检测。
层叠太多,过度绘制
跟 measure 一样, View 的绘制也是从树根开始一层一层往叶子绘制,就难免导致叶子的绘制挡住了其父节点的一些绘制的内容。过渡绘制是一个术语,表示某些组件在屏幕上的一个像素点的绘制次数超过 1 次。过度绘制导致的问题是花了太多的时间去绘制那些堆叠在下面的、用户看不到的东西,浪费了 CPU 周期和渲染时间。
如何查看是否过渡绘制
- 打开手机的开发者选项
- 找到 调试GPU过度绘制
- 选择 显示过度绘制区域
此时我们可以看到屏幕上"丰富多彩"了。
蓝色,淡绿,淡红,深红代表了4种不同程度的 Overdraw 情况,我们的目标就是尽量减少红色 Overdraw,看到更多的蓝色甚至白色区域。
如何优化
- 去除重复或者不必要的 background
- 点击态中的 normal 尽量设置成 transparent
- 去除 window 中的 background(这个可以通过处理 decorView 或者设置 Theme 的方式)
- 若是自定义控件的话,通过 canvas.clipRect() 帮助系统识别那些可见的区域
负载过重
UI 线程是应用的主线程,很多的性能和卡顿问题是由于在主线程中做了大量的工作。除了主线程外,子线程占用过多 CPU 资源也会导致渲染性能问题。
在 UI 渲染的过程中,是 CPU 和 GPU 共同合作完成的,其中 CPU 负责把 UI 组件计算成 Polygons,Texture 纹理,然后交给 GPU 进行栅格化渲染。
GPU 呈现模式分析
打开方法:
- 打开手机的开发者选项
- 找到 GPU呈现模式分析
- 选择 在屏幕上显示为条形图
此时我们可以看到屏幕的上放或下方多了各种颜色的条形图。
各颜色含义:
个人简析
GPU呈现模式主要方便看到连续的界面绘制是否超过16ms,不方便看每个界面详细的绘制时间,推荐使用Hierarchy Viewer查看详细的具体是绘制时间。
内存抖动
主要导致原因是频繁创建大对象或者频繁创建大量对象,并且这些对象属于用完就废弃的,比如 byte[] 。
优化
- 大对象可以使用对象池复用,比如 byte[]
- 尽量在 16ms 内少创建对象,比如在 onDraw 中创建 Paint 对象,decode Bitmap 之类的
硬件加速
并非所有的都支持硬件加速,其中包括 clipPath() 等;同时也有一些方法在开启硬件加速之后与不开启硬件加速效果不一样,比如 drawBitmapMesh() 等。
Application 级别
<applicationandroid:hardwareAccelerated = "true" ...>
Activity 级别
<activity android:hardwareAccelerated = "true" ...>
Window 级别
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
View 级别
View.setLayerType(View.LAYER_TYPE_HARDWARE, null);