造成卡顿的原因有很多种,但最终都会反应到CPU时间上,CPU时间分为用户时间和系统时间。
- 用户时间:执行应用代码所需时间。
- 系统时间:执行内核态系统代码所需时间,如I/O、锁、中断。
问题:当出现卡顿的时候,如何区分是应用的问题,还是系统的问题?
1. 卡顿问题分析指标
- CPU使用率 首先应该先查看CPU使用率,通过/proc/[pid]/stat可以得到某个进程的CPU使用情况,会得到utime和stime两个重要字段。应用系统时间若超过30%,则应检查是I/O过多还是其它系统调用问题。
Android中常用Linux命令:
| 命令 | 作用 |
|---|---|
| top | 查看哪个进程是CPU消耗大户 |
| vmstat | 实时监视虚拟内存和CPU活动 |
| strace | 跟踪某个进程的所有系统调用 |
- CPU饱和度
这个标反应线程排队等待CPU的情况,也就是CPU负载,该指标跟应用线程数有关,线程数过多会导致系统频繁切换CPU上下文。也跟线程优先级有关,CPU线程调度会对执行效率有非常大影响。
可以使用vmstat命令或/proc/[pid]/schedstat文件来查看CPU上下文切换次数。
uptime命令可反应CPU在1分钟、5分钟、15分钟内的平均负载,建议控制在0.7 * 核数内。proc/self/sched: nr_voluntary_switches: 主动上下文切换次数,因为线程无法获取所需资源导致上下文切换,最普遍的是IO。 nr_involuntary_switches: 被动上下文切换次数,线程被系统强制调度导致上下文切换,例如大量线程在抢占CPU。 se.statistics.iowait_count:IO 等待的次数 se.statistics.iowait_sum: IO 等待的时间
2. 卡顿问题分析工具
-
systrace工具 它在一些系统调用上添加了探针,性能开销低,但不支持应用程序代码的耗时分析,要想在systrace上增加应用程序的耗时分析,可以通过编译时给每个函数插桩的方式实现,利用到Trace.beginSection和Trace.endSection两个接口来统计应用程序代码耗时。
-
SimplePerf工具 它可以看到所有Native代码的耗时,也封装了systrace的功能,Android P以后的版本可无缝支持,使用火焰图的方式展示分析结果。
-
AndroidStudio自带的Profiler 支持Call Chart和火焰图两种方式展示分析结果。
3. 卡顿的监控
主线程监控。
-
消息队列 因为looper在轮询处理的时候会打印日志,所以可以通过给looper设置一个自定义的Printer来统计耗时。
// This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } ... //事件处理结束 if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); }另一种方案是通过一个监控线程每隔一秒就向主线程消息队列头部添加一条空消息,假设 1 秒后这个消息并没有被主线程消费掉,说明阻塞消息运行的时间在 0~1 秒之间。换句话说,如果我们需要监控 3 秒卡顿,那在第 4 次轮询中头部消息依然没有被消费的话,就可以确定主线程出现了一次 3 秒以上的卡顿。
-
systrace编译插桩 通过在函数入口和出口添加耗时监控代码实现监控,因为监控代码是一样的,所以要给每个方法添加独立的ID作为参数,过滤return、i++等简单函数,对于调用频繁的函数不计入统计减少性能损耗。缺点是无法监控系统代码的耗时。
-
profilo FaceBook开源的库,功能强大,性能基本没有影响,捕捉信息全,集成了atrace的功能,可快速获取Java堆栈,需要注意兼容性。
其它监控
- 帧率监控 重点获取冻帧率这个指标,连续丢帧42帧以上称为冻帧,冻帧率就是冻帧时间在所有时间的占比。发生丢帧时获取当前页面信息、view信息、操作路径上报后台帮助排查。还可以根据activity、fragment分组采集平均帧率、冻帧率统计对比。
- 生命周期监控 监控Activity、Service、Receiver生命周期的耗时和调用次数;监控进程生命周期的启动次数和耗时,可以看出某些进程是否被频繁拉起。监控手段:插件化技术Hook、编译时插桩(Aspect、ASM、ReDex)。
- 线程监控 其它线程过多占用CPU会影响主线程的UI响应能力,因此应该监控如下两个指标。 第一个:线程数量,监控线程数量的多少和创建方式。 第二个:线程时间,监控用户时间utime、系统时间stime和优先级,主要看哪些线程占用过多CPU。