如何做卡顿优化
我们不说废话,直接来干货,在项目中做卡顿优化一般需要分三步走
-
- 需要了解卡顿的原理
-
- 需要了解工具的使用,例如设置里面的GPU呈现工具,用于检查每一帧的耗时和每一帧具体做了什么事情以及他们的耗时,还有Systrace,JVMTI,cpu profile,btrace, Hierarchy View等
-
- 需要根据工具的检查结果和卡顿针对业务情况具体解决
卡顿原理
绘制流程
在编舞者接收到Vsync信号后,会触发Handler的消息调度,发送异步消息,触发输入事件处理,动画处理,以及绘制流程,触发组件的measure,layout和draw的过程,在draw的过程中,会生成一系列绘制指令,然后存在在一个DisplayList Buffer缓存区中,而且每个View内部都有一个DisplayList,当View需要重绘的时候,这个View会被标记为dirty,在draw的时候,这些View的DisplayList都会被更新,这个时候的DisplayList就会保存了最新的绘制指令,这些事情都是在主线程完成的,在绘制流程走完,生成一系列的DisplayList 绘制指令后,这些绘制指令会被同步到渲染线程,这些都是在UI线程完成的
DisplayList 可以认为是一系列绘制指令,如下,TextView中调用了drawRect()和drawText()后会生成一些绘制指令,这些绘制指令会被同步到渲染线程中等待后续OpenGL渲染引擎和GPU的处理
渲染流程
在生成DisplayList后,DisplayList会同步到渲染线程RenderThread中,RenderThread在android5.0后引入,用于实现openGL渲染任务和GPU的任务,在android5.0之前,这些都是在UI线程完成,也就是说RenderThread减轻的UI线程的负担,提升了渲染效率,整个渲染流程如下
- OpenGl指令转换:绘制指令转OpenGl指令
- 指令Buffer交换:OpenGl指令移交到GPU执行
- GPU处理:GPU对数据处理的过程
- 获取Graphic Buffer:SurfaceFlinger会为我们托管一个BufferQuen缓冲区队列,在光栅化前会从BufferQuen 中获取Graphic Buffer图形缓冲区,用于存放光栅化后的图像纹理数据
- 光栅化:将顶点数据或几何图元转化为像素纹理,并确定每个像素纹理在屏幕像素网格中的位置的过程
- 加入Graphic Buffer:将光栅化后的像素纹理加入到Graphic Buffer图形缓冲区中
- 加入BufferQuene:将Graphic Buffer图形缓冲区加入到BufferQuene队列中,等待SurfaceFlinger进程消费
SurfaceFlinger 生成屏幕图像
将Graphic Buffer图形缓冲区加入到BufferQuene队列中后,会通知 SurfaceFlinger进程消费Graphic Buffer图形缓冲区数据,SurfaceFlinger 通过Swap Buffer 把 Front Graphic Buffer 的图形缓冲区数据移交到Hardware Composer HAL (HWC)硬件合成层中,和其他的图形缓冲区数据合成一个新的图像,例如状态栏和导航栏,然后推到屏幕的FrameBuffer缓冲区中等待展示,流程如下
卡顿原因
布局上的UI元素经过上面的绘制流程,渲染流程以及SurfaceFlinger 生成屏幕图像的过程后会展示到屏幕上,而安卓系统一般是每16毫秒渲染一帧,也就是说如果在16ms每没有完成一帧的渲染,就是卡顿掉帧,绘制流程或者渲染流程中如果其中一些阶段时间比较长,导致16ms内没有完成一帧数据的生成,就会导致卡顿掉帧
性能分析工具
Systrace
可以通过帧颜色分析掉帧情况,UI线程和渲染线程的耗时情况,CPU的使用情况
JVMTI
不仅可以监控对象的分配和回收,线程的分配和回收,还能监听一定时间内GC的次数,解决GC过于频繁而导致UI卡顿掉帧的问题,Android Studio Profiler 的内存监控就是基于 JVMTI实现 的
GPU 渲染模式分析工具
使用直方图展示每一帧的耗时和使用不同的颜色块展示渲染流水线中每个阶段的耗时,从而分析性能瓶颈
Btrace
使用方式类似于Systrace,但是和Systrace不同的是它会对不同线程的不同业务代码执行的方法进行自动埋点,Systrace需要开发人员手动埋点,这样一来UI线程所有的耗时行为和慢函数都能知道,用来帮助解决住线程的耗时行为和慢函数,而且它还分析锁竞争导致的UI线程阻塞,监控线程的创建
如何解决
1 布局优化
1.自定义view使用merge减少布局嵌套
2.使用ConstraintLayout替代 RelativeLayout和LinearLayout,ConstraintLayout的性能更好,而且还能有效减少布局嵌套,虽然RelativeLayout也能减少布局嵌套,但是由于RelativeLayout需要对子view计算两次,因此使用RelativeLayout减少布局嵌套不一定能带来跟好的性能体验
3.使用ViewStub包装待显示的view,提升加载速度,被ViewStub包装的view不会参与到绘制流程,只有inflater后才会参与到绘制流程
4.避免反复渲染大图,因为图片渲染需要把图片的内存从CPU的内存区域转移到GPU的内存区域,并且需要GPU转化成像素纹理,反复渲染大图会给GPU造成很大的压力
5.自定义view使用canvas.clipRect限制绘制区域,减少CPU和GPU的消耗
2 避免过度绘制
1 移除没必要的背景色和背景图片
2 自定义view的时候使用canvas.clipRect限制绘制区域,减少cpu和gpu的压力
3 避免主线程的耗时行为
1 注意避免主线程的慢函数,有些业务逻辑比较复杂,执行起来会比较耗时,应该避免在ui刷新的时候执行这些慢函数,等待ui刷新结束后才执行这些慢函数
2 避免在主线程执行IO操作
3 避免主线程的耗时计算
4 优化动画
1 尽量使用属性动画替代补间动画和帧动画,属性动画的性能更好,补间动画 会导致view重绘非常频繁,帧动画需要依赖图片资源,当图片很多或者图片较大的时候就会导致内存占用很大,性能很差
2 属性动画设置离屏缓存,离频缓存就是开启一个独立的内存区域,然后把view绘制到这个内存区域,后续做动画的过程中复用这些缓存,减少重复绘制,提升动画性能,缓存的结果是opengl的纹理数据或者bitmap,取决于是否开启了硬件加速,但是设置离屏缓存后内存占用会变大,但是只针对View内容不发生变更的操作,也就是View中无需使用 invalidate方法的操作,比如位移、旋转、大小等操作
3 使用ViewPropertyAnimator实现动画,设置ViewPropertyAnimator的ViewPropertyAnimatorRT 属性将动画的构建和绘制迁移到RenderThread渲染线程中,实现异步渲染动画,减少主线程压力,同时还能防止主线程繁忙导致动画卡顿
4 使用SurfaceView在子线程实现动画,SurfaceView通过独立的SurfaceHolder提供双缓冲机制,支持在子线程中直接绘制,避免主线程阻塞
public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private Thread drawThread;
public CustomSurfaceView(Context context) {
super(context);
getHolder().addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
drawThread = new Thread(() -> {
while (true) {
Canvas canvas = holder.lockCanvas();
// 在canvas上绘制动画帧
holder.unlockCanvasAndPost(canvas);
}
});
drawThread.start();
}
}
5 合理使用内存
1 避免内存占用太大,导致gc,避免 主线程和渲染线程被挂起,导致卡顿
2 避免频繁创建对象导致gc和内存抖动,导致卡顿
6 谨慎使用多线程
谨慎使用多线程,尤其是UI刷新的时候,一定要尽量避免主线程和其他线程抢锁,因为这会导致主线程阻塞,导致卡顿
7.充分利用CPU
1 让UI线程或者渲染线程绑定CPU大核,可以参考 速度优化:绑定 CPU 大核目前手机的 CPU 都是多核的,比如骁龙 8gen3 这款 CPU 就有 8 个核心,其中大 - 掘金
参考
Android渲染系列(1)之原理概述篇【Android资深开发,阿里& 字节面试官】本篇文章主要宏观整体的介绍Andro - 掘金
安卓性能优化---绘制优化篇UI绘制优化是性能优化中非常重要的一部分,因为用户在使用应用过程的中,优秀的交互体验是我们留 - 掘金
一文读懂直播卡顿优化那些事儿希望本文可以带给大家一个相对全局的视角看待卡顿问题,认识到卡顿是什么、卡顿的成因、卡顿的分类 - 掘金
属性动画与硬件加速_动画开启硬件加速 android-CSDN博客