页面布局渲染现状
在开发项目时,由于Android机型各式各样,显示的页面布局也随之不一样,这个时候xml布局文件就会越写越乱。
获取页面布局文件耗时:
startTime = System.currentTimeMillis();
setContentView(R.layout.car_series_revision_activity);
long durTime = System.currentTimeMillis() - startTime;
LogHelper.e("布局总耗时","页面布局耗时:" + durTime);
结果如下:
卡顿的原因
1.Android绘制原理
► 1.Android的屏幕刷新中涉及到最重要的三个概念
(1)CPU:执行应用层的measure、layout、draw等操作,绘制完成后将数据提交给GPU
(2)GPU:进一步处理数据,并将数据缓存起来
(3)屏幕:由一个个像素点组成,以固定的频率(16.6ms,即1秒60帧)从缓冲区中取出数据来填充像素点
总结一句话就是:CPU 绘制后提交数据、GPU 进一步处理和缓存数据、最后屏幕从缓冲区中读取数据并显示。
► 2.双缓冲机制
当布局比较复杂,或设备性能较差的时候,CPU并不能保证在16.6ms内就完成绘制数据的计算,所以这里系统又做了一个处理。
当你的应用正在往Back Buffer中填充数据时,系统会将Back Buffer锁定。
如果到了GPU交换两个Buffer的时间点,你的应用还在往Back Buffer中填充数据,GPU会发现Back Buffer被锁定了,它会放弃这次交换。
这样做的后果就是手机屏幕仍然显示原先的图像,这就是我们常常说的掉帧。
2.布局加载原理
页面启动时,布局加载在主线程上进行耗时操作,会导致页面渲染及加载慢。
布局加载主要通过setContentView来实现,下面是它的调用时序图:
我们可以看到,在setContentView中主要有两个耗时操作:
(1)解析xml,获取XmlResourceParser,这是IO过程。
(2)通过createViewFromTag,创建View对象,用到了反射。
以上两点就是布局加载慢的原因,也是布局的性能瓶颈。
布局加载优化
我们的优化方式主要有以下两种:
(1)异步加载,将布局加载过程转移到子线程
(2)去掉IO和反射过程
1.异步加载,AsyncLayoutInflater方案
setContentView 默认是在UI主线程加载布局的,其加载过程中的耗时操作,如解析xml,反射创建view对象等也是在主线程执行,AsyncLayoutInflater 可以让这些加载过程在子线程中执行,这样可以提高UI线程的响应性,UI线程同时可以进行其他操作。AsyncLayoutInflater使用方式如下:
new AsyncLayoutInflater(this).inflate(R.layout.car_series_revision_activity, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
setContentView(view);
}
});
AsyncLayoutInflater方案的缺点:
(1) UI布局和view的初始化在子线程中进行,如果view还未初始化成功,在主线程中再调用view会引起崩溃。
(2) 一般情况下,主线程会调用view,涉及到大量子线程和主线程在view调用上的同步问题,这就牺牲了易用性,代码可维护性也会变差。
(3) 如果是在老页面逻辑结构上引入AsyncLayoutInflater进行改造,结构改动很大,很容易发生view调用崩溃错误,不太可行。
2.X2C方案
X2C 是掌阅开源的一套布局加载框架。X2C的主要思路是利用apt工具,在编译期将我们写的xml布局文件解析成view,并根据xml动态设置view的各类属性,这样,我们在运行时,调用findViewById,根据view id拿到的view,已经是直接new 出来的view,避免了运行时的xml IO操作和反射操作,这就解决了布局时的耗时问题。
原始的xml布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns