大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。Android系统每隔大概16.6ms发出VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成。
Android系统每隔16ms就会发送一个VSYNC信号(VSYNC:vertical synchronization 垂直同步,帧同步),触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的正常帧率:60fps。一旦这时候系统正在做大于16ms的耗时操作,系统就会无法响应VSYNC信号,执行渲染工作,导致发生丢帧现象。
关于Android屏幕刷新机制可以参考Android屏幕刷新机制
用户容易在UI执行动画、ListView、RecyclerView滑动的时候感知到界面的卡顿与不流畅现象。所以开发者一定要注意在设计布局时不要嵌套太多层,多使用 include方法引入布局。同时不要让动画执行次数太多,导致CPU或者GPU负载过重。
对于布局特别复杂的页面,可以采用异步加载布局的方式来优化提升加载效率。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);
//......
}
});
需要注意:
过渡绘制是指屏幕上某个像素在同一帧的时间内绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制操作,这就会导致某些像素区域被绘制了多次,这就是很大程度上浪费了CPU和GPU资源。最最常见的过度绘制,就是设置了无用的背景颜色!!!
对于Overdraw这个问题还是很容易发现的,我们可以通过以下步骤打开显示GPU过度绘制(Show GPU Overrdraw)选项
设置 -> 开发者选项 -> 调试GPU过度绘制 -> 显示GPU过度绘制
打开以后之后,你会发现屏幕上有各种颜色,此时你可以切换到需要检测的程序与界面,对于各个色块的含义,请看下图:
蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况:
我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。
Systrace 是Android平台提供的一款工具,用于记录短期内的设备活动。该工具会生成一份报告,其中汇总了Android 内核中的数据,例如 CPU 调度程序、磁盘活动和应用线程。Systrace主要用来分析绘制性能方面的问题。在发生卡顿时,通过这份报告可以知道当前整个系统所处的状态,从而帮助开发者更直观的分析系统瓶颈,改进性能。
在抓取systrace文件的时候,切记不要抓取太长时间,也不要太多不同操作。
打开抓取的html文件,可以看到我们应用存在非常严重的掉帧,不借助工具直接用肉眼看应用UI是看不出来的。如果只是单独存在一个红色或者黄色的都是没关系的。关键是连续的红/黄色或者两帧间隔非常大那就需要我们去仔细观察。按“W” 放大视图,在UIThread(主线程)上面有一条很细的线,表示线程状态。
Systrace 会用不同的颜色来标识不同的线程状态, 在每个方法上面都会有对应的线程状态来标识目前线程所处的状态。通过查看线程状态我们可以知道目前的瓶颈是什么, 是 CPU 执行慢还是因为 Binder 调用, 又或是进行 IO 操作,又或是拿不到 CPU 时间片。 通过查看线程状态我们可以知道目前的瓶颈是什么, 是 CPU 执行慢还是因为 Binder调用, 又或是进行 IO 操作, 又或是拿不到CPU事件片。
线程状态主要有下面几个:
紫色:表示休眠,一般表示IO;
橙色:不可中断的休眠
紫色:可中断的休眠
白色:表示休眠,可能是因为线程在互斥锁上被阻塞 ,如Binder堵塞/Sleep/Wait等
其实对于APP开发而言,使用systrace的帮助并不算非常大,大部分内容用于设备真机优化之类的系统开发人员观察。systrace无法帮助应用开发者定位到准确的错误代码位置,我们需要凭借很多零碎的知识点与经验来猜测问题原因。如果我们有了大概怀疑的具体的代码块或者有想了解的代码块执行时系统的状态,还可以结合 Trace API 打标签。
Android 提供了Trace API能够帮助我们记录收集自己应用中的一些信息 : Trace.beginSection() 与 Trace.endSection();
public class MainActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TraceCompat.beginSection("enjoy_launcher"); //Trace.beginSection()
setContentView(R.layout.activity_main);
TraceCompat.endSection(); //Trace.endSection()
} }
Android主线程更新UI。如果界面1秒钟刷新少于60次,即FPS小于60,用户就会产生卡顿感觉。简单来说, Android使用消息机制进行UI更新,UI线程有个Looper,在其loop方法中会不断取出message,调用其绑定的 Handler在UI线程执行。如果在handler的dispatchMesaage方法里有耗时操作,就会发生卡顿。
只要检测 msg.target.dispatchMessage(msg) 的执行时间,就能检测到部分UI线程是否有耗时的操作。注意到这行 执行代码的前后,有两个logging.println函数,如果设置了logging,会分别打印出>>>>> Dispatching to和 <<<<< Finished to 这样的日志,这样我们就可以通过两次log的时间差值,来计算dispatchMessage的执行时 间,从而设置阈值判断是否发生了卡顿。
Looper 提供了 setMessageLogging(@Nullable Printer printer) 方法,所以我们可以自己实现一个Printer,在 通过setMessageLogging()方法传入即可
这种方式也就是 BlockCanary 原理。
开启trace日志抓取,并将日志信息存储到APP目录下data/data/app包名。
Debug.startMethodTracingSampling(new File(getExternalFilesDir(""),"trace").getAbsolutePath(),8*1024*1024,1_000);
停止日志抓取
Debug.stopMethodTracing()
启动APP,执行trace日志抓取后会生成一个trace文件,将trace文件拖到Android Studio中,可以通过右侧TopDown分析方法执行的耗时时间