“工欲善其事,必先利其器”,在开始着手分析卡顿问题之前,我们还必须要掌握一些分析性能问题的工具与手段,也就是掌握分析问题所使用的“器”,才能帮助我们更好的观测系统运行的状态,找到性能问题的原因。Systrace
是谷歌提供的最常用的Android
系统级性能分析工具。它以视觉化的方式将系统内所有的“工作线程”(内核+框架+应用)与系统模块状态(如 CPU
调度、IO
活动、Binder
调用等信息)按时间轴铺开展示,并支持在 Chrome
浏览器中显示出来。在 Perfetto
出现之前,它是最实用、分析最精准、最专业的性能分析工具,是分析Android
性能问题的首选。本文我们就来详细分析一下这个Systrace
工具 。
1 Systrace内容结构
Systrace
可以收集Android关键子系统(如CPU
调度、SurfaceFlinger
渲染系统、System Server
系统框架、Input
系统等)的运行信息,并以图像的形式按照时间轴铺开展示出来,帮助开发者直观的看到整个系统的运行状态,从而分析出系统的性能问题瓶颈所在。其界面展示的主要信息区域如下:
1.1 CPU Trace信息区域
如上图所示,Systrace
中 CPU Trace
一般在最上面显示,展示Kernel
中的 CPU Info
区域信息,一般包含如下信息:
CPU
的组成架构,包含多少颗CPU
运行核心,以及编号信息。- 每颗
CPU
核心的实时运行频率信息; - 每颗
CPU
核心当前支持的运行频率的最高与最低的门限值; - 每颗
CPU
核心的状态信息,是否进入节能或断电关闭状态; - 每颗
CPU
核心上运行的线程任务信息与统计,按时间轴排开
总的来说,Systrace
中的CPU Trace
这里一般是看任务调度信息,查看是否是CPU
频率或者是CPU
调度逻辑导致当前任务出现性能问题,举例如下:
- 某个场景的任务执行比较慢,我们就可以查看是不是这个任务被
CPU
调度器安排到了小核上运行? - 某个场景的任务执行比较慢,当前执行这个任务的
CPU
运行频率是不是不够?是否因为门限值的设置不合理导致CPU
被限频了? - 我的任务对性能要求比较高,比如指纹解锁,能不能把我这个任务持续放到
CPU
超大核去运行? - 我的前台应用任务线程长时间处于
Runnable
状态无法执行而卡顿,当前到底是什么任务在抢占了CPU
资源在运行?
1.2 渲染显示系统信息区域
在这篇文章Android卡顿掉帧问题分析之原理篇-CSDN博客我们完整的分析了Android
应用上帧显示的全部流程,其中包含了Android
渲染显示系统的核心逻辑,这部分内容在Systrace
上也有完整的展示,如下图所示:
从上面的Systrace
截图中我们可以看到一个屏幕Vsync
周期内(屏幕刷新率60HZ
条件下就是16.6ms
),应用上帧渲染显示的完整流程信息:
Vsync app
信号到来后唤醒应用App
的UI
线程,开始执行一帧画面的measure
、layout
、draw
绘制等动作;- 在
HWUI
硬件加速默认开启的条件下,应用App
的UI
线程完成draw
绘制动作后就会唤醒其RenderThread
渲染线程,触发其使用SkiaOpenGL
渲染管道执行一帧数据的渲染操作,渲染完成后通过Binder
调用queueBuffer
接口将Buffer
画面缓存数据提交到SurfaceFlinger
进程; SurfaceFlinger
进程中在收到应用queueBuffer
上帧请求后,会先将对应的应用Layer
中的BufferQueue
中的Buffer
置为Queued
状态,标识应用“生产”了一帧Buffer
画面数据,然后通过requestNextVsync
注册下一个Vsync sf
信号;Vsync sf
信号到达后,会及时唤醒SurfaceFlinger
进程的主线程执行一帧的合成任务,其中会先遍历所有的Layer
图层缓存区,找到处于Queued
状态的新提交的缓存区,将其置为Acquired
状态,标识“消费”了一帧Buffer
画面数据;然后Binder
唤醒HWC service
进程的工作线程执行具体的图层的合成送显操作;
总的来说,Systrace
中的渲染显示系统这部分能够帮助我们判断界面是否有掉帧以及掉帧产生原因的分析方向,主要分析流程如下图所示:
1.3 System Server框架进程信息区域
1.3.1 Input
Input
是 System Server
进程中非常重要的一部分,主要是由 InputReader
和 InputDispatcher
这两个 Native
子线程组成,关于这一部分在上一篇文章中已经从原理机制并结合Systrace
详细分析过,这里就不再展开分析。我们回顾一下这边部分内容在Systrace
上的表现:
1.3.2 ActivityManagerService
ActivityManagerService
(以下简称AMS
)属于系统框架System Server
中运行的核心服务之一,主要负责应用进程和四大组件的管理。是与应用App
进程之间产生Binder
交互最多的系统服务之一。谷歌原生的AOSP
代码中就在AMS
服务管理的进程和四大组件的各种场景下有对应的Trace
点来记录,比如大家熟悉的 ActivityStart
、ActivityResume
、activityStop
等,与AMS
相关的Trace
一般会用TRACE_TAG_ACTIVITY_MANAGER
这个TAG
,在 Systrace
中对应的名字是 ActivityManager
。以桌面打开应用冷启动场景为例,AMS
需要为应用创建一个新的进程,此过程在AOSP
代码中就有相关的AMS
相关的Trace
埋点记录,如下图所示:
对应Systrace
上表现如下图所示:
1.3.3 WindowManagerService
WindowManagerService
(以下简称WMS
) 属于系统框架System Server
中运行的核心服务之一,主要负责应用窗口管理、窗口动画管理、Surface
管理和Input
触控事件的管理。也是与应用App
进程之间产生Binder
交互最多的系统服务之一。谷歌原生的AOSP
代码中就在WMS
窗口管理等核心流程上有对应的Trace Tag
埋点来记录。与WMS
相关的 Trace
一般会用 TRACE_TAG_WINDOW_MANAGER
这个 TAG
, 在 Systrace
中对应的名字是 WindowManager
。继续以上面的桌面打开应用冷启动场景为例,应用启动后创建第一个Activity
界面后,在绘制第一帧画面时需要通过Binder
访问框架WMS
服务的relayoutWindow
接口,以实现计算应用窗口的大小和申请一张可用Surface
画布等逻辑,从Systrace
上看如下图所示:
1.3.4 HandlerThread核心工作线程
上面描述的AMS
、WMS
等系统框架核心管理服务,其具体逻辑最终都需要运行在具体的线程中。为此system_server
进程中设计了很多继承于HandlerThread
(自带Looper
消息循环的线程)的核心工作线程,负责执行不同分类业务的逻辑。比如有专门负责系统UI
展示的UiThread
线程(源码实现位于framework/base/services/core/java/com/android/server/UiThread.java
),线程名为“android.ui
”,在Systrace
上的显示如下图所示:
比如负责窗口动画展示的AnimationThread
,线程名为“android.anim
”,在Systrace
上的显示如下图所示:
还有很多,比如负责IO
处理的工作线程“android.io
”,再比如拥有极高优先级,负责系统界面实时显示的工作线程“android.display
”等,由于篇幅所限,就不再一一展开分析,感兴趣的读者可以自行结合源码去阅读。随着对系统源码逻辑的深入了解,相信对这些框架的核心工作线程也会越来越熟悉。
由于这些自带消息循环的核心工作线程中执行的都是系统框架system_server
进程中的核心逻辑,所以这些工作线程运行的是否通畅直接关系到系统的“健康程度”。通过从Systrace
上观察这些工作线程的运行任务负载就可以直接反映出系统框架的负载与繁忙程度,如果这些线程出现长时间的阻塞,轻则导致系统反应变慢、界面卡顿等问题,重则会导致系统冻屏。为此Android
系统设计在开机时会将一些系统框架核心工作线程加入到WatchDog
的监控中,通过定期往这些线程消息循环队列中抛消息,然后检查消息是否得到及时执行来判断其是否正常顺畅运行,如果出现长时间超时得不到执行就会导致系统重启。相关简化代码如下:
/*frameworks/base/services/core/java/com/android/server/Watchdog.java*/
private Watchdog() {
...
// The shared foreground thread is the main checker. It is where we
// will also dispatch monitor checks and do other work.
mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
"foreground thread", DEFAULT_TIMEOUT);
mHandlerCheckers.add(mMonitorChecker);
// Add checker for main thread. We only do a quick check since there
// can be UI running on