学习Gracker Systrace系列文章,总结使用Systrace分析App性能的方法。推荐想通过Systrace学习Framework的同学,去看原文。
# 同线程使用
Trace.beginSection("onCreate");
...
Trace.endSection();
# 异步线程使用
Trace.beginAsyncSection()
...
Trace.endAsyncSection()
注意:如果时间太短不会捕捉到,在命令行捕捉时,必须添加-a $applicationId 大范围添加建议使用TraceFix。打开Android_SDK\tools\monitor.bat
使用chrome打开报错
在网上也没有找到对应的解决办法
使用命令行方式非常简单
注意:这里需要安装python2并配置环境变量
推荐. windows10同时安装python2和python3,完美解决冲突
adb shell atrace -h # 查看支持的所有选项
D:\Android_SDK\platform-tools\systrace>adb shell atrace -h
atrace: invalid option -- h
usage: atrace [options] [categories...]
options include:
-a appname enable app-level tracing for a comma separated list of cmdlines; * is a wildcard matching any process
-b N use a trace buffer size of N KB
-c trace into a circular buffer
-f filename use the categories written in a file as space-separated
values in a line
-k fname,... trace the listed kernel functions
-n ignore signals
-s N sleep for N seconds before tracing [default 0]
-t N trace for N seconds [default 5]
-z compress the trace dump
--async_start start circular trace and return immediately
--async_dump dump the current contents of circular trace buffer
--async_stop stop tracing and dump the current contents of circular
trace buffer
--stream stream trace to stdout as it enters the trace buffer
Note: this can take significant CPU time, and is best
used for measuring things that are not affected by
CPU performance, like pagecache usage.
--list_categories
list the available tracing categories
-o filename write the trace to the specified file instead
of stdout.
D:\Android_SDK\platform-tools\systrace>
一般来说比较常用的是
快捷键的使用可以加快查看 Systrace 的速度,下面是一些常用的快捷键
鼠标模式快捷切换 : 主要是针对鼠标的工作模式进行切换 , 默认是 1 ,也就是选择模式,查看 Systrace 的时候,需要经常在各个模式之间切换 , 所以点击切换模式效率比较低,直接用快捷键切换效率要高很多
数字键1 : 切换到 Selection 模式 , 这个模式下鼠标可以点击某一个段查看其详细信息, 一般打开 Systrace 默认就是这个模式 , 也是最常用的一个模式 , 配合 M 和 ASDW 可以做基本的操作
W : 放大 Systrace , 放大可以更好地看清局部细节
S : 缩小 Systrace, 缩小以查看整体
A : 左移
D : 右移
M : 高亮选中当前鼠标点击的段(这个比较常用,可以快速标识出这个方法的左右边界和执行时间,方便上下查看)
数字键2 : 切换到 Pan 模式 , 这个模式下长按鼠标可以左右拖动, 有时候会用到
数字键3 : 切换到 Zoom 模式 , 这个模式下长按鼠标可以放大和缩小, 有时候会用到
数字键4 : 切换到 Timing 模式 , 这个模式下主要是用来衡量时间的,比如选择一个起点, 选择一个终点, 查看起点和终点这中间的操作所花费的时间。
这是分析App性能的最明显的特征
绿色 : 运行中(Running)
蓝色 : 可运行(Runnable)
白色 : 休眠中(Sleep)
线程没有工作要做,可能是因为线程在互斥锁上被阻塞。
橘色 : 不可中断的睡眠态 (Uninterruptible Sleep - IO Block)
线程在I / O上被阻塞或等待磁盘操作完成
紫色 : 不可中断的睡眠态(Uninterruptible Sleep)
线程在另一个内核操作(通常是内存管理)上被阻塞。
注意:Uninterruptible指的是当前不能响应外部中断
一个线程被唤醒的信息往往比较重要,知道他被谁唤醒,那么我们也就知道了他们之间的调用等待关系,如果一个线程出现一段比较长的 sleep 情况,然后被唤醒,那么我们就可以去看是谁唤醒了这个线程,对应的就可以查看唤醒者的信息,看看为什么唤醒者这么晚才唤醒。
另外一个场景的情况是:应用主线程在等待此应用的其他线程执行的结果,这时候线程唤醒信息就可以用来分析主线程到底被哪个线程 Block 住了,比如下面这个场景,这一帧 doFrame 执行了 152ms,有明显的异常,但是大部分时间是在 sleep
发现在这个过程中,有间歇性的Runnable,对其进行放大,点击Runnable,查看说明信息
看看20424是哪一个线程,将分析面板滑动到Kernel部分,在同一个时间节点在所有的CPU上都点击一下,看看CPU Slice-》Args tid一致。
然后针对性分析RenderHeartbeat方法调用过程。
User Expectation 信息解析
位于整个 Systrace 最上面的部分,标识了 Rendering Response 和 Input Response
显示的锁的信息
ActivityTaskManagerService 的 getFocusedStackInfo 方法在执行过程中被阻塞,原因是因为执行同步方法块的时候,没有拿到同步对象的锁的拥有权;需要等待拥有同步对象的锁拥有权的另外一个方法 ActivityTaskManagerService.activityPaused 执行完成后,才能拿到同步对象的锁的拥有权,然后继续执行
作为移动应用开发者这一块接触不多,Systrace作为整体分析安卓系统性能的工具,如果不了解一点,有些工作挺难开展,捡性能分析用到的简单说说。
InputReader 和 InputDispatcher 是跑在 SystemServer 里面的两个 Native 线程,负责读取和分发 Input 事件,我们分析 Systrace 的 Input 事件流,首先是找到这里。
下面针对上图中标号进行简单说明
下面以第一个 Input_Down 事件的处理流程来进行详细的工作流说明,其他的 Move 事件和 Up 事件的处理是一样的(部分不一样,不过影响不大)
从上面的 Systrace 来看,Input 事件的基本流向如下:
只要记住Input事件的流动方向即可,如果在App端出现多个事件堆积,就发生App卡顿。
三者协调配合产生流畅的画面。其工作过程如图所示:
Systrace时序图
Vsync 信号可以由硬件产生,也可以用软件模拟,不过现在基本上都是硬件产生,负责产生硬件 Vsync 的是 HWC,HWC 可生成 VSYNC 事件并通过回调将事件发送到 SurfaceFlinge , DispSync 将 Vsync 生成由 Choreographer 和 SurfaceFlinger 使用的 VSYNC_APP 和 VSYNC_SF 信号。
VSync灰色和白色都是一个完整的Vsync
Choreographer 的引入,主要是配合 Vsync,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机。
如果强制关闭硬件加速器就没有RenderThread,都在UI thread进行。
SurfaceFlinger 也会在 Vsync 到来的时候,将所有已经准备好的 Surface 进行合成操作。
Choreographer(RenderThread)与SurfaceFlinger是生产者、消费者关系。总共三个Buffer,存在彼此制约的关系。任何一个缓慢都会造成另外一个“卡住”。
通常我们通过 Systrace 判断应用是否掉帧的时候,一般是直接看 SurfaceFlinger 部分,主要是下面几个步骤
应用自身原因主要是应用启动时候的组件初始化、View 初始化、数据初始化耗时等,具体包括:
如果是应用自身的原因,可以使用 TraceView(AS 自带的 CPU Profiler)、Simple Perf 等继续查看更加详细的函数调用信息,也可以使用 TraceFix 插件,插入更多的 TraceTag 之后,重新抓取 Systrace 来对比分析
应用启动后,SystemServer 会记录从 startActivity 被调用到应用第一帧显示的时长,在 Systrace 中的显示如下(注意结尾是应用第一帧,如果应用启动的时候是 SplashActivity -> MainActivity,那么这里的结尾只是 SplashActivity,MainActivity 的完全启动需要自己查看)
通常的大型应用,App 冷启动通常包括下面三个部分,每一个部分耗时都会导致应用的整体启动速度变慢,所以在优化启动速度的时候,需要明确知道应用启动结束的点(需要跟测试沟通清楚,一般是界面保持稳定的那个点)
由于是冷启动,所以 App 进程在 Fork 之后,需要首先执行 bindApplication ,这个也是区分冷热启动的一个重要的点。Application 的环境创建好之后,就开始组件的启动(这里是 Activity 组件,通过 Service、Broadcast、ContentProvider 组件启动的进程则会在 bindApplication 之后先启动这些组件)
Activity 的生命周期函数会在 Activity 组件创建的时候执行,包括 onStart、onCreate、onResume 等,然后还要经过一次 Choreographer#doFrame 的执行(包括 measure、layout、draw)以及 RenderThread 的初始化和第一帧任务的绘制,再加上 SurfaceFlinger 一个 Vsync 周期的合成,应用第一帧才会真正显示(也就是下图中 finishDrawing 的位置)
大部分的 App 都有 SplashActivity 来播放广告,播放完成之后才是真正的主 Activity 的启动,同样包括 Activity 组件的创建,包括 onStart、onCreate、onResume 、自有启动逻辑的执行、WebView 的初始化等等等等,直到主 Activity 的第一帧显示
一般来说,主 Activity 需要多帧才能显示完全,因为有很多资源(最常见的是图片)是异步加载的,第一帧可能只加载了一个显示框架、而其中的内容在准备好之后才会显示出来。这里也可以看到,通过 Systrace 不是很方便来判断应用冷启动的终点(除非你跟测试约定好,在某个 View 显示之后就算启动完成,然后你在这个 View 里面打个 Systrace 的 Tag,通过跟踪这个 Tag 就可以粗略判断具体 Systrace 里面哪一帧是启动完成的点)
这个在前面有提到
# 同线程使用
Trace.beginSection("ccc");
...
Trace.endSection();
# 异步线程使用
Trace.beginAsyncSection()
...
Trace.endAsyncSection()
注意:如果时间太短不会捕捉到,在命令行捕捉时,必须添加-a $applicationId 大范围添加建议使用TraceFix。
在启动优化的过程中,idleHandler 可以在 MessageQueue 空闲的时候执行任务,配合activity.reportFullyDrawn获取页面完成时间。
使用代码如下:
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onResume() {
super.onResume();
mHandler.getLooper().getQueue().addIdleHandler(() -> {
reportFullyDrawn();
return false;
});
}
}