文章目录
- 一.Android绘制原理
- View绘制过程
- 双缓冲机制
- 布局加载原理
- 布局加载优化
- 1. AsyncLayoutInflater方案
- 2. X2C方案
- 3. Compose方案
- 二.布局优化
- 三.绘制优化
- 1. 去掉多余背景色,减少复杂shape的使用
- 2. 自定义View使用clipRect屏蔽被遮盖View绘制
- 3.onDraw 中不要创建新的局部对象。
- 4.onDraw方法中不要做耗时的任务,
一.Android绘制原理
View绘制过程
- CPU:执行应用层的measure、layout、draw等操作,绘制完成后将数据提交给GPU
- GPU:进一步处理数据,并将数据缓存起来
- 屏幕:由一个个像素点组成,以固定的频率(16.6ms,即1秒60帧)从缓冲区中取出数据来填充像素点
总结一句话就是:CPU 绘制后提交数据、GPU 进一步处理和缓存数据、最后屏幕从缓冲区中读取数据并显示。
双缓冲机制
看完上面的流程图,我们很容易想到一个问题,屏幕是以16.6ms的固定频率进行刷新的,但是我们应用层触发绘制的时机是完全随机的 。
如果在GPU向缓冲区写入数据的同时,屏幕也在向缓冲区读取数据,怎么办?所以,在屏幕刷新中,Android系统引入了双缓冲机制, GPU只向Back Buffer中写入绘制数据,且GPU会定期交换Back Buffer和Frame Buffer,交换的频率也是60次/秒,这就与屏幕的刷新频率保持了同步。
虽然我们引入了双缓冲机制,但是我们知道,当布局比较复杂,或设备性能较差的时候,CPU并不能保证在16.6ms内就完成绘制数据的计算,所以这里系统又做了一个处理。当你的应用正在往Back Buffer中填充数据时,系统会将Back Buffer锁定。如果到了GPU交换两个Buffer的时间点,你的应用还在往Back Buffer中填充数据,GPU会发现Back Buffer被锁定了,它会放弃这次交换。这样做的后果就是手机屏幕仍然显示原先的图像,这就是我们常常说的掉帧
布局加载原理
由上面可知,导致掉帧的原因是CPU无法在16.6ms内完成绘制数据的计算。而之所以布局加载可能会导致掉帧,正是因为它在主线程上进行了耗时操作,可能导致CPU无法按时完成数据计算,布局加载主要通过setContentView来实现,一起来看看它的时序图
布局加载优化
1. AsyncLayoutInflater方案
syncLayoutInflater 是来帮助做异步加载 layout 的, 默认情况下 setContentView 函数是在 UI 线程执行的,其中有一系列的耗时动作:Xml的解析、View的反射创建等过程同样是在UI线程执行的,AsyncLayoutInflater 就是来帮我们把这些过程以异步的方式执行,保持UI线程的高响应。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new AsyncLayoutInflater(AsyncLayoutActivity.this)
.inflate(R.layout.async_layout, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
setContentView(view);
}
});
// 别的操作
}
这样做的优点在于将UI加载过程迁移到了子线程,保证了UI线程的高响应 缺点在于牺牲了易用性,同时如果在初始化过程中调用了UI可能会导致崩溃
2. X2C方案
X2C是掌阅开源的一套布局加载框架。
它的主要是思路是在编译期,将layout翻译生成对应的java文件,这样对于开发人员来说写布局还是写原来的xml,但对于程序来说,运行时加载的是对应的java文件。
使用时如下所示,使用X2C.setContentView替代原始的setContentView即可
// this.setContentView(R.layout.activity_main);
X2C.setContentView(this, R.layout.activity_main);
X2C优点
- 在保留xml的同时,又解决了它带来的性能问题
- 据X2C统计,加载耗时可以缩小到原来的1/3
X2C问题
- 部分属性不能通过代码设置,Java不兼容
- 将加载时间转移到了编译期,增加了编译期耗时
3. Compose方案
Compose 是 Jetpack 中的一个新成员,Compose使用纯kotlin开发,使用简洁方便,Compose是未来android UI开发的方向
二.布局优化
布局优化的本质就是减少View的层级。常见的布局优化方案如下:
-
使用ConstraintLayout,可以实现完全扁平化的布局,减少层级
-
嵌套的LinearLayout中,尽量不要使用weight,因为weight会重新测量两次
-
在LinearLayout和RelativeLayout都可以完成布局的情况下优先选择RelativeLayout,可以减少View的层级,但是注意相同组件可能RelativeLayout绘制时间长
-
使用 < include > 标签将常用的布局组件共同的部分抽取出来,以便复用。
-
通过 < ViewStub > 标签来加载不常用的布局,延迟加载(需要的时候在activity中加载出来)
-
使用 < Merge > 标签来减少布局的嵌套层次
三.绘制优化
绘制优化是指View的onDraw方法要避免执行大量的操作,这主要体现在两个方面:
1. 去掉多余背景色,减少复杂shape的使用
2. 自定义View使用clipRect屏蔽被遮盖View绘制
3.onDraw 中不要创建新的局部对象。
因为onDraw方法可能会被频繁调用,这样就会在一瞬间产生大量的临时对象,这不仅占用了过多的内存而且还会导致系统更加频繁gc,降低了程序的执行效率。
4.onDraw方法中不要做耗时的任务,
不能执行成千上万次的循环操作,尽管每次循环都很轻量级,但是大量的循环仍然十分抢占CPU的时间片,这会造成View的绘制过程不流畅。
按照Google官方给出的性能优化典范中的标准,View的绘制频率保证60fps是最佳的,这就要求每帧绘制时间不超过16ms(16ms = 1000/60),虽然程序很难保证16ms这个时间,但是尽量降低onDraw方法中的复杂度总是切实有效的。