继上篇继续性能优化,本文概要:
大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。Android系统每隔大概16.6ms发出VSYNC信
号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,
这意味着程序的大多数操作都必须在16ms内完成。
如果某个操作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那
么用户在32ms内看到的会是同一帧画面。
一般主线程过多的UI绘制、大量的IO操作或是大量的计算操作占用CPU,导致App界面卡顿。
Systrace 是Android平台提供的一款工具,用于记录短期内的设备活动。该工具会生成一份报告,其中汇总了
Android 内核中的数据,例如 CPU 调度程序、磁盘活动和应用线程。Systrace主要用来分析绘制性能方面的问
题。在发生卡顿时,通过这份报告可以知道当前整个系统所处的状态,从而帮助开发者更直观的分析系统瓶颈,改
进性能。
要使用Systrace,需要先安装 Python2.7。安装完成后配置环境变量 path ,随后在命令行输入 python -- version
进行验证
其实Systrace对于应用开发者来说,能看的并不多。主要用于看是否丢帧,与丢帧时系统以及我们应用大致的一个
状态。
python systrace.py -t 5 -o F:\example\a.html gfx input view am dalvik sched wm
disk res -a com.miss.example
systrace可以让我们了解应用所处的状态,了解应用因为什么原因导致的。若需要准确分析卡顿发生在什么函数,资源占用情况如何,目前业界两种主流有效的app监控方式如下:
Android主线程更新UI。如果界面1秒钟刷新少于60次,即FPS小于60,用户就会产生卡顿感觉。简单来说,
Android使用消息机制进行UI更新,UI线程有个Looper,在其loop方法中会不断取出message,调用其绑定的
Handler在UI线程执行。如果在handler的dispatchMesaage方法里有耗时操作,就会发生卡顿
public static void loop() {
//......
for (;;) {
//......
Printer logging = me.mLogging;
if (logging != null) { // 此处记录开始
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) { // 此处记录结束
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
//......
}
}
loop函数每个执行操作都会记录其开始与结束,根据这个可以得到这个操作用了多长时间,这是BlockCanary的原理
BlockCanary传送门在此
Android系统每隔16ms发出VSYNC信号,来通知界面进行重绘、渲染,每一次同步的周期约为16.6ms,代表一帧
的刷新频率。通过Choreographer类设置它的FrameCallback函数,当每一帧被渲染时会触发回调
FrameCallback.doFrame (long frameTimeNanos)
函数。frameTimeNanos是底层VSYNC信号到达的时间戳 。
public class ChoreographerHelper {
public static void start() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
long lastFrameTimeNanos = 0;
@Override
public void doFrame(long frameTimeNanos) {
//上次回调时间
if (lastFrameTimeNanos == 0) {
lastFrameTimeNanos = frameTimeNanos;
Choreographer.getInstance().postFrameCallback(this);
return;
}
long diff = (frameTimeNanos - lastFrameTimeNanos) / 1_000_000;
if (diff > 16.6f) {
//掉帧数
int droppedCount = (int) (diff / 16.6);
}
lastFrameTimeNanos = frameTimeNanos;
Choreographer.getInstance().postFrameCallback(this);
}
});
}
}
}
通过 ChoreographerHelper 可以实时计算帧率和掉帧数,实时监测App页面的帧率数据,发现帧率过低,还可以自动保存现场堆栈信息。
measure、layout、draw这三个过程都包含自顶向下的View Tree遍历耗时,如果视图层级太深自然需要更多的时间来完成整个绘测过程,从而造成启动速度慢、卡顿等问题。而onDraw在频繁刷新时可能多次出发,因此onDraw更不能做耗时操作,同时需要注意内存抖动。对于布局性能的检测,依然可以使用systrace与traceview按照绘制流程检查绘制耗时函数
位置在这
可以很清晰的看到布局的层级。我们应该尽量减少其层级,可以使用 ConstraintLayout 约束布局使得布局尽量扁平化,移除非必需的UI组件。
当LayoutInflater遇到这个标签时,它会跳过它,并将内的元素添加到的父元素里。
可以参考这篇大佬的文章
viewstub是一个轻量级的view,它不可见,不用占用资源,只有设置viewstub为visible或者调用其inflater()方法时,其对应的布局文件才会被初始化。
过度绘制是指系统在渲染单个帧的过程中多次在屏幕上绘制某一个像素。例如,如果我们有若干界面卡片堆叠在一起,每张卡片都会遮盖其下面一张卡片的部分内容。但是,系统仍然需要绘制堆叠中的卡片被遮盖的部分。
手机开发者选项中能够显示过度渲染检查功能,通过对界面进行彩色编码来帮我们识别过度绘制。开启步骤如下:
Android 将按如下方式为界面元素着色,以确定过度绘制的次数:
LayoutInflater加载xml布局的过程会在主线程使用IO读取XML布局文件进行XML解析,再根据解析结果利用反射创建布局中的View/ViewGroup对象。这个过程随着布局的复杂度上升,耗时自然也会随之增大。Android为我们提供了 Asynclayoutinflater 把耗时的加载操作在异步线程中完成,最后把加载结果再回调给主线程。
dependencies {
implementation "androidx.asynclayoutinflater:asynclayoutinflater:1.0.0"
}
new AsyncLayoutInflater(this)
.inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
setContentView(view);
//......
}
});