攻略大全
1. 粘贴攻略
1.1 Android绘制原理
1.1.1 绘制原理概述
CPU 负责计算显示内容,即执行应用层的measure、layout、draw等操作,绘制完成后将数据提交给GPU。
GPU 负责栅格化(UI元素绘制到屏幕上),即进一步处理数据,并将数据缓存起来。
屏幕 由一个个像素点组成,大多数的Android设备屏幕以固定的频率60Hz(16.6ms,即1秒60帧)从缓冲区中取出数据来填充像素点。
简而言之:CPU绘制后提交数据、GPU进一步处理和缓存数据、最后屏幕从缓冲区中读取数据并显示。
示意图如下:
1.1.2 双缓冲机制
屏幕是以16.6ms的固定频率进行刷新的,但是应用层触发绘制的时机是完全随机的(比如我们随时都可以触摸屏幕触发绘制)。 如果在GPU向缓冲区写入数据的同时,屏幕也在向缓冲区读取数据,这时的屏幕画面必然是异常的。
为了避免这个问题,在屏幕刷新中,Android系统引入了双缓冲机制。
GPU只向BackBuffer中写入绘制数据,且GPU会定期交换Back Buffer和Frame Buffer,交换的频率也是60次/秒,这就与屏幕的刷新频率保持了同步。
示意图如下:
1.1.3 掉帧
虽然引入了双缓冲机制,但是当布局比较复杂,或设备性能较差的时候,CPU并不能保证在16.6ms内就完成绘制数据的计算,所以这里系统又做了一个处理。
当你的应用正在往Back Buffer中填充数据时,系统会将Back Buffer锁定。 如果到了GPU交换两个Buffer的时间点,你的应用还在往Back Buffer中填充数据,GPU会发现Back Buffer被锁定了,它会放弃这次交换。
这样做的后果就是手机屏幕仍然显示原先的图像,这就是我们常常说的掉帧。
2. 造火箭攻略
2.1 布局加载原理
2.1.1 布局加载原理流程简述
-
向Activity设置布局文件后,会调用LayoutInflater的inflate()方法,这个方法中会通过Resources.getLayout()获取到xml资源解析器。
-
有了xml资源解析器后,紧接着调用createViewFromTag()方法,而这个方法最终会调用Factory的oCreateView()方法。
-
Factory的oCreateView()方法会回调至相关实现类AppCompatDelegateImpl的onCreateView()方法。
-
onCreateView()方法的具体实现中可以看到到它先是通过反射获取AppCompatViewInflater的实例,并调用AppCompatViewInflater的createView()方法。
-
而AppCompatViewInflater的createView()方法中,可以看到它最终通过传参所获得的XML中Viwe的Tag来匹配进而实例化出不同类型的View。
-
如果没有匹配上对应的Tag,比如自定义View,它依然是以反射的形式实例化自定义View。
2.1.2 布局加载原理流程图示
setContentView()时序图如下:
setContentView()流程图如下:
setContentView()-->LayoutInflater-->inflate-->-->getLayout-->crateViewFromTag-->Factory-->createView-->反射创建View
在setContentView中主要有两个耗时操作:
- 解析xml,获取XmlResourceParser,这是IO过程。
- 通过createViewFromTag,创建View对象,用到了反射。 而这两点就是布局加载可能导致卡顿的原因,也是布局的性能瓶颈。
2.2 优化工具
2.2.1 Systrace
Systrace是Android 4.1中新增的性能数据采样和分析工具,它可以帮助开发者收集Android关键子系统(SurfaceFlinger、WMS等Framework部分关键模块、服务,View体系系统等)的运行信息。Systrace 的功能包括跟踪系统的I/O 操作、内核工作队列、CPU 负载以及Android各个子系统的运行状况等。对于UI显示性能,比如动画播放不流畅、渲染卡顿等问题提供了分析数据。
2.2.1.1 在DDMS中使用Systrace
2.2.1.2 用命令行使用Systrace
Android提供一个Python脚本文件systrace.py,它位于Android SDK目录/tools/systrace中,我们可以执行以下命令来使用Systrace:
-
$ cd android-sdk/platform-tools/systrace
-
$ python systrace.py --time=10 -o newtrace.html sched gfx view wm
2.2.1.3 3.在代码中使用Systrace
Systrace并不会追踪应用的所有工作,在Android 4.3及以上版本的代码中,可以使用Trace类对应用中的具体活动进行追踪,API18以上使用,推荐TraceCompat。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TraceCompat.beginSection("start_test");
Log.d(TAG,"onCreate==>>"+getTaskId());
long start = System.currentTimeMillis();
setContentView(R.layout.activity_main);
long inflateTime = System.currentTimeMillis()-start;
View view = findViewById(R.id.iv_child);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(SecondActivity.this,JavaMainActivity.class));
}
});
TraceCompat.endSection();
}
beginSection方法和endSection方法之间的代码会被追踪,endSection 方法会只会结束最近的beginSection 方法,因此要保证beginSection方法和endSection方法的调用次数要相同。
2.1.3.4 用Choreme分析Systrace
我们可以使用W键和S键进行放大和缩小,A键和D键进行左右移动。
由于Systrace 是以系统的角度返回一些信息的,只能为我们提供一个概览,它的深度是有限的,我们可以用它来进行粗略的检查,以便了解大概的情况。
如果要分析更详细的,比如要找到是什么让CPU繁忙,某些方法的调用次数等,则还要借助另一个工具traceView。
但是traceview已被CPU Profiler所替代,不过CPU Profiler的具体使用方式与traceview一模一样,也可以说traceview已被优化集成到Profiler中。
2.2.2 LayoutInspector
- Android Studio自带工具
- 查看视图层次结构
2.2.3 Choreographer
- 获取fps,
- 线上使用
- 具备实时性
- 需要Api16以上
/**
* Posts a frame callback to run on the next frame.
* <p>
* The callback runs once then is automatically removed.
* </p>
*
* @param callback The frame callback to run during the next frame.
*
* @see #postFrameCallbackDelayed
* @see #removeFrameCallback
*/
public void postFrameCallback(FrameCallback callback) {
postFrameCallbackDelayed(callback, 0);
}
2.2.4 Profile GPU Rendering(GPU呈现模式)
Profile GPU Rendering是Android 4.1系统提供的开发辅助功能,我们可以在开发者选项中打开这一功能,如下图所示。
图中横轴代表时间,纵轴表示某一帧的耗时。绿色的横线为警戒线,超过这条线则意味着时长超过了16m,尽量要保证垂直的彩色柱状图保持在绿线下面。这些垂直的彩色柱状图代表着一帧,不同颜色的彩色柱状图代表不同的含义。
-
Swap Buffers:是CPU告诉GPU渲染一帧的地方,这是一个阻塞调用,因为CPU会一直等待GPU发出接到命令的回复,如果橙色柱状图很高,则表明GPU很繁忙。
-
Command Issue:表示执行的时间,这部分是Android进行2D渲染Display List的时间。如果红色柱状图很高,可能是由于重新提交了视图而导致的。还有复杂的自定义View也会导致红的柱状图变高。
-
Sync&Upload:表示的是准备当前界面上有待绘制的图片所耗费的时间,为了减少该段区域的执行时间,我们可以减少屏幕上的图片数量或者缩小图片的大小。
-
Draw:表示测量和绘制视图列表所需要的时间,如果蓝色柱状图很高,可能需要重新绘制,或者View的onDraw方法处理事情太多。
-
Measure/Layout:表示布局的onMeasure与onLayout所花费的时间,一旦时间过长,就需要仔细检查自己的布局是不是存在严重的性能问题。
-
Animation:表示计算执行动画所需要花费的时间,包含的动画有ObjectAnimator、ViewPropertyAnimator、Transition等。一旦这里的执行时间过长,就需要检查是不是使用了非官方的动画工具或者检查动画执行的过程中是不是触发了读/写操作等。
-
Input Handling:表示系统处理输入事件所耗费的时间,粗略等于对事件处理方法所执行的时间。一旦执行时间过长,意味着在处理用户的输入事件的地方执行了复杂的操作。
-
Misc Time/Vsync Delay:表示在主线程中执行了太多的任务,导致UI渲染跟不上VSYNC的信号而出现掉帧的情况。
2.3 获取布局文件加载耗时的操作
2.3.1 常规获取
2.3.2 AOP(Aspectj,ASM,ArtHook)
2.3.3 获取任一控件耗时
2.4 布局加载优化方案介绍
优化思路
- 侧面缓解(异步加载)
- 根本解决(不需要IO,反射过程,如X2C,Anko,Compose等)
2.4.1 AsyncLayoutInflater
- 原理:子线程中加载布局而后回调至主线程
- 缺陷:final类,项目中若使用应进行二次定制开发,满足项目需求
2.4.2 Java代码写布局
本质上解决了性能问题,但是又引入了新问题:不便于开发、可维护性差
2.4.3 X2C
X2C优点:
- 保留Xml优点,解决其性能问题
- 开发人员写xml,加载Java代码
- 原理:APT编译期间翻译xml为Java代码
X2C问题:
- 部分属性不支持
- 失去了系统的兼容性(AppCompact)
- 属于开源框架,项目中使用需要进行二次定制开发
2.4.4 Anko
Anko使用上比较方便同时性能较高,但是比起XML方式改动很大,同时Anko已经放弃维护了
2.4.5 Compose
Compose是未来android UI开发的方向,是替换XML的有效手段
2.5 最终优化方案
以上介绍的方案是从布局加载耗时方面来考虑的,而在一般项目中,其实布局加载耗时并不是主要耗时的地方,优化收益不大。 最重要的是,当所在团队重在求稳,团队方案技术难以更新迭代时,此时选择常规的优化方案才是最合适的,毕竟大道至简。
过渡绘制指标如下:
2.5.1 常规优化之优化布局层级及复杂度
- 使用ConstraintLayout,可以实现完全扁平化的布局,减少层级。
- RelativeLayout本身尽量不要嵌套使用。
- 嵌套的LinearLayout中,尽量不要使用weight,因为weight会重新测量两次。
- 推荐使用merge标签,可以减少一个层级。
- 使用ViewStub延迟加载。
2.5.2 常规优化之避免过度绘制
- 去掉多余背景色,减少复杂shape的使用。
- 避免层级叠加。
- 自定义View使用clipRect屏蔽被遮盖View绘制。
2.5.3 降低View.OnDraw()复杂度
- onDraw()中不要创建新的局部对象
- 避免onDraw()执行大量耗时操作
2.5.4 常规优化总结
根据笔者的优化实践来看,随着优化进度的进行,你会顺带这发现一些问题:
- 无用的动画效果代码早已在界面上无法看到,却没有被去除掉,依然顽强运行
- 布局中一些早已无效的View依然留在布局中,只是被新布局直接盖住了,并没有删除
- 层层叠叠的背景图设置 这是一个老项目迭代过程中,很容易产生的问题。 把这些顺带的问题给解决,并把相关问题记录与汇报,再讨论解决,布局优化到最后,你会发现,自己的App终于不再是满眼红彤彤,满满的成就感定会油然而生。