Perfetto详解
Perfetto介绍
Perfetto 是一个生产级的开源堆栈,用于提高性能 仪器和痕量分析。与 Systrace 不同,它提供数据源超集,可以用 protobuf 编码的二进制流形式记录任意长度的跟踪记录。可以将Perfetto理解为systrace的升级版,用在更新的平台、新图表展示更多的信息。它可帮助开发者收集 Android 关键子系统(如SurfaceFlinger/SystemServer/Input/Display 等 Framework 部分关键模块、服务,View系统等)的运行信息,从而帮助开发者更直观的分析系统瓶颈,改进性能。
主要特征:
可以在线抓取长时间的trace:可以长达一个小时,另外抓取的文件size也可以达到GB级别,这样就可以在后台开启,让它一直抓取trace了,特别适用于那种复现概率很低,又比较严重的性能问题。
Perfetto具有很好的可扩展性:它除了提供标准的tracepoints之外,例如CPU调度信息,内存信息等,还可以通过atrace HAL层扩展,在Android P当中,Google新增加了一个atrace HAL层,atrace进程可以调用这个HAL的接口来获取当前的扩展信息,相关代码可见Google 提交,这样如果需要扩展tracepoints的话,就可以按照graphic的示例添加即可。
提供全新的Perfetto UI网站:可以在上面通过选取开关的方式,自动生成抓取trace的命令,同时可以打开trace文件,自动把protobuf或者json转变成UI,另外还集成了几种预定义的trace分析统计工具,详情可见它的 Metrics and auditors 选项。
Perfetto本身是一个框架,关于它的架构和模块的详细介绍,有兴趣的可以参考它的doc网站,它的源码可以参考Android Source Tree的 /external/perfetto 目录,里面有很多的tools和脚本,可以拿来直接使用,本文只对这个工具做简单的使用介绍。
doc网站地址:https://perfetto.dev/docs/
抓取方法
如果遇到不能进入root权限手机抓取trace,则可以看看这篇文章。抓取trace-CSDN博客
从手机端直接抓取
打开系统跟踪
方法一:在开发者选项中打开。
打开开发者选项。
进入开发者选项,选择系统跟踪(System tracing),开启显示“快捷设置”图块(Show Quick Setting tile)。
打开顶部下滑菜单快捷设置面板的快捷图标,或者打开开发者选项系统跟踪(System tracing)的录制跟踪记录(Record trace)。
通知面板会出现一个系统跟踪通知,点击可以开始和停止录制。
方法二:通过adb命令打开。
adb shell am start com.android.traceur/com.android.traceur.Main Activity
对应界面如下:
选中显示“快捷设置”图块,会在控制中心显示快速抓取trace图块:
开始录制
数据保存位置/data/local/traces
上传trace文件
adb pull /data/local/traces .
命令抓取perfetto-traces方式
命令行形式比较灵活,速度也比较快,相关的TAG一次性配置好之后,以后再使用的时候就会很快就出结果,也可以把相关命令写成脚本,下次使用会比较方便。
命令相关的参数虽然比较多,但使用工具的时候不需考虑这么多,在对应的项目前打钩即可,命令行的时候才会去手动加参数。
一般来说比较常用的是:
-o : 指示输出文件的路径和名字。
-t : 抓取时间(最新版本可以不用指定, 按 Enter 即可结束)。
-b : 指定 buffer 大小 (一般情况下,默认的 Buffer 是够用的,如果你要抓很长的 Trae , 那么建议调大 Buffer )。
-a : 指定 app 包名 (如果要 Debug 自定义的 Trace 点, 记得要加这个)。
在低于Android R的版本上面默认是关闭的,需要先执行命令打开:
adb shell setprop persist.traced.enable 1
基本抓取并导出perfetto-traces命令如下,需要其他tag或者相关命令可以自行加入:
抓取perfetton:
adb shell perfetto -o /data/misc/perfetto-traces/trace_file.perfetto-trace -t 10s sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory
-o /data/misc/perfetto-traces/trace_file.perfetto-trace
:输出trace的路径
-t 10s
:最多抓取时长,可以Ctrl+C停止
sched ... memory
:要抓取相关模块
导出trace文件:
adb pull /data/misc/perfetto-traces/trace_file.perfetto-trace
通过Perfetto网页自定义抓取
通过Perfetto UI网站可以帮助生成配置文件,点击 Record new trace会看到很多配置界面。
方法1:自动生成命令,然后终端执行。
选择想要的tracepoints之后,点击Recording command
,将命令拷贝出来直接在终端就可以执行,完成之后分析/data/misc/perfetto-traces/trace
文件。
adb shell perfetto \
-c - --txt \
-o /data/misc/perfetto-traces/trace \
<<EOF
buffers: {
size_kb: 63488
fill_policy: DISCARD
}
buffers: {
size_kb: 2048
fill_policy: DISCARD
}
data_sources: {
config {
name: "android.packages_list"
target_buffer: 1
}
}
data_sources: {
config {
name: "android.power"
android_power_config {
battery_poll_ms: 1000
battery_counters: BATTERY_COUNTER_CAPACITY_PERCENT
battery_counters: BATTERY_COUNTER_CHARGE
battery_counters: BATTERY_COUNTER_CURRENT
collect_power_rails: true
}
}
}
data_sources: {
config {
name: "linux.process_stats"
target_buffer: 1
process_stats_config {
scan_all_processes_on_start: true
}
}
}
data_sources: {
config {
name: "linux.sys_stats"
sys_stats_config {
stat_period_ms: 1000
stat_counters: STAT_CPU_TIMES
stat_counters: STAT_FORK_COUNT
cpufreq_period_ms: 1000
}
}
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "power/suspend_resume"
ftrace_events: "sched/sched_wakeup"
ftrace_events: "sched/sched_wakeup_new"
ftrace_events: "sched/sched_waking"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
ftrace_events: "power/gpu_frequency"
ftrace_events: "raw_syscalls/sys_enter"
ftrace_events: "raw_syscalls/sys_exit"
ftrace_events: "sched/sched_process_exit"
ftrace_events: "sched/sched_process_free"
ftrace_events: "task/task_newtask"
ftrace_events: "task/task_rename"
buffer_size_kb: 2048
drain_period_ms: 250
}
}
}
duration_ms: 10000
EOF
方法2:网页连接设备直接抓取。
“Add ADB Device” 选择手机设备,如下图,其中有一些参数配置,根据自己的需要添加修改。
最后点击右上角 “Start Recording”。
源码抓取perfetto
使用源码中自带的record_android_trace工具也可以抓取,具体的record_android_trace路径是在源码的如下目录:
源码路径/perfetto/tools
具体抓取命令:
./record_android_trace -o $(date +%Y%m%d_%H%M%S)_trace_file.perfetto-trace -t 10s -b 32mb sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory gfx view wm am ss video camera hal res sync idle binder_driver binder_lock ss
以上抓取后直接自己会打开相关的chrome的浏览器。
使用atrace命令抓取systrace
抓取atrace命令:
adb shell atrace -z -b 40000 am wm view res ss gfx view halbionic pm sched freq idle disk load sync binder_driver binder_lock memreclaim dalvik input -t 10 > /data/local/tmp/trace_output.atrace
pull导出trace文件:
adb pull /data/local/tmp/trace_output.atrace
使用systrace脚本抓取systrace
需要配置好python环境。
解压systrace.zip。
进入到解压后的systrace目录下,此时该目录下可以找到systrace.py文件,执行如下命令:
python systrace.py am wm view res ss gfx rs hal bionic pm sched freq idle disk binder_driver binder_lock memreclaim dalvik input database -t 10 -o tracelog\systrace.html
在systrace目录下的tracelog目录中查找生成的systrace.html文件。
查看线程状态
Perfetto 会用不同的颜色来标识不同的线程状态, 在每个方法上面都会有对应的线程状态来标识目前线程所处的状态,通过查看线程状态我们可以知道目前的瓶颈是什么, 是 cpu 执行慢还是因为 Binder 调用, 又或是进行 io 操作, 又或是拿不到 cpu 时间片。
线程状态主要有下面几个
绿色 : 运行中(Running)
只有在该状态的线程才可能在 cpu 上运行。而同一时刻可能有多个线程处于可执行状态,这些线程的 task_struct 结构被放入对应 cpu 的可执行队列中(一个线程最多只能出现在一个 cpu 的可执行队列中)。调度器的任务就是从各个 cpu 的可执行队列中分别选择一个线程在该cpu 上运行。作用:经常会查看 Running 状态的线程,查看其运行的时间,与竞品做对比,分析快或者慢的原因:
是否频率不够?
是否跑在了小核上?
是否频繁在 Running 和 Runnable 之间切换?为什么?
是否频繁在 Running 和 Sleep 之间切换?为什么?
是否跑在了不该跑的核上面?比如不重要的线程占用了超大核?
蓝色 : 可运行(Runnable)
线程可以运行但当前没有安排,在等待 cpu 调度。作用:Runnable 状态的线程状态持续时间越长,则表示 cpu 的调度越忙,没有及时处理到这个任务:
是否后台有太多的任务在跑?
没有及时处理是因为频率太低?
没有及时处理是因为被限制到某个 cpuset 里面,但是 cpu 很满?
此时 Running 的任务是什么?为什么?
白色 : 休眠中(Sleep)
线程没有工作要做,可能是因为线程在互斥锁上被阻塞,也可能等待某个线程返回,可以看是被谁唤醒,去查看是等待哪个线程。作用 : 这里一般是在等事件驱动。
橘色 : 不可中断的睡眠态 (Uninterruptible Sleep - IO Block)
线程在I / O上被阻塞或等待磁盘操作完成,一般底线都会标识出此时的 callsite :wait_on_page_locked_killable。作用:这个一般是标示 io 操作慢,如果有大量的橘色不可中断的睡眠态出现,那么一般是由于进入了低内存状态,申请内存的时候触发 pageFault, linux 系统的 page cache 链表中有时会出现一些还没准备好的 page(即还没把磁盘中的内容完全地读出来) , 而正好此时用户在访问这个 page 时就会出现 wait_on_page_locked_killable 阻塞了. 只有系统当 io 操作很繁忙时, 每笔的 io 操作都需要等待排队时, 极其容易出现且阻塞的时间往往会比较长。
棕色 : 不可中断的睡眠态( Uninterruptible Sleep (non-IO))
线程在另一个内核操作(通常是内存管理)上被阻塞。作用:一般是陷入了内核态,有些情况下是正常的,有些情况下是不正常的,需要按照具体的情况去分析。
任务唤醒信息分析
Perfetto 会标识出一个非常有用的信息,可以帮助我们进行线程调用等待相关的分析。一个线程被唤醒的信息往往比较重要,知道他被谁唤醒,那么我们也就知道了他们之间的调用等待关系,如果一个线程出现一段比较长的 sleep 情况,然后被唤醒,那么我们就可以去看是谁唤醒了这个线程,对应的就可以查看唤醒者的信息,看看为什么唤醒者这么晚才唤醒。
一个常见的情况是:应用主线程程使用 Binder 与 SystemServer 的 AMS 进行通信,但是恰好 AMS 的这个函数正在等待锁释放(或者这个函数本身执行时间很长),那么应用主线程就需要等待比较长的时间,那么就会出现性能问题,比如响应慢或者卡顿,这就是为什么后台有大量的进程在运行,或者跑完 Monkey 之后,整机性能会下降的一个主要原因。
另外一个场景的情况是:应用主线程在等待此应用的其他线程执行的结果,这时候线程唤醒信息就可以用来分析主线程到底被哪个线程 Block 住了,如下场景:
我们可以看到这段slepping状态这段时间,我们可以点击后面段Runnig状态,查看它运行在哪个CPU上,点击后可以看到是哪个线程唤醒它的,就可以看是否有异常情况了。
信息区数据解析
CPU架构
简单来说目前的手机 CPU 按照核心数和架构来说,可以分为下面三类:
非大小核架构(正常所以核一样)。
大小核架构(正常0-3小核,4-7大核)。
大中小核架构(正常0-3小核,4-6中核,7超大核)。
CPU Info信息
Pefetto 中CPU Info信息一般在最上面,里面经常会用到的信息包括:
CPU 频率变化情况。
任务执行情况。
大小核的调度情况。
CPU Boost 调度情况。
总的来说,Systrace 中的 Kernel CPU Info 这里一般是看任务调度信息,查看是否是频率或者调度导致当前任务出现性能问题,举例如下:
某个场景的任务执行比较慢,我们就可以查看是不是这个任务被调度到了小核?
某个场景的任务执行比较慢,当前执行这个任务的 CPU 频率是不是不够?
我的任务比较特殊,能不能把我这个任务放到大核去跑?
我这个场景对 CPU 要求很高,我能不能要求在我这个场景运行的时候,限制 CPU 最低频率?
Current Selection信息解析
CPU by thread信息解析
刷新率
60 fps 的意思是说,画面每秒更新60次,是针对软件的。
这60次更新,是要均匀更新的,不是说一会快,一会慢,那样视觉上也会觉得不流畅。
每秒60次,也就是 1/60 ~= 16.67 ms 要更新一次。
这里说的屏幕的刷新率,是针对硬件的,现在大部分手机屏幕的刷新率,都维持在60 HZ,移动设备上一般使用60HZ,是因为移动设备对于功耗的要求更高,提高手机屏幕的刷新率,对于手机来说,逻辑功耗会随着频率的增加而线性增大,同时更高的刷新率,意味着更短的TFT数据写入时间,对屏幕设计来说难度更大。
trace分析方法
打开trace文件
抓到 Perfetto Trace 之后,一般是在 Perfetto UI 中打开(如果用官方提供的脚本,则会在抓去结束后自动在这个网站上打开)。打开后界面如下:
在Open trace file中打开pull出来的trace。
打开以后,就可以分析其中的数据:
大致上 Perfetto Trace 界面可以分为四个区域:
最右边的操作区:这里最主要的是 Current Trace 这一栏下面的那几个会经常用到。
Show timeline :显示当前 Trace,切到了别的界面之后,再点这个就会回到 Trace View 界面
Query:写 SQL 查询语句和查看执行结果的地方
Metrics:官方默认帮你写好的一些分析结果,可以选择直接打开
Info and stats :当前 Trace 和手机 App 的一些信息
上方的信息和操作区域:最主要就是看时间。
中间的 Trace 内容区:操作最多的区域,Trace 内容都在这部分,最上面的几部分是从功能的角度来划成一个区域的,比如 CPU 区(可以查看当前 Task 跑在哪个核心上,频率是多少,跑了多长时间、被谁唤醒)、Ftrace event 区等;下面的就是以 App Process 为单位展示的(包括 App 的各种线程、Input 事件、Binder Call、Memory、Buffer 等信息)。
最下方的信息区:这个区域主要是展示各种信息、我们选中了某个 Task 段之后,这里就会展示这个 Task 相关的信息(如果你加了 Log,这里也会显示 Log;ftrace event 同理)。
查看方法
基本操作通过“W”、“S”、“A”、“D”实现,英文键盘下,w可以实现对trace范围的放大,s可以缩小范围,a整体左移,d整体右移。基本操作还有疑问,可以在页面中shift+/ ,也就是“?”,通过官方提供的操作指南可以学习基本操作。
左键单击某个线程,既可看到详细信息,例如耗时等。再shift+M,可以精准标注该线程,使用鼠标左键长按整个区间,选择在需要分析的部分长按,既可获取在此期间的各个进程的操作,如图所示就是在still capture期间内CPU负载的具体情况,如果想看此时CPU 频率,左键长按在CPU Frequency上既可查看期间内频率的变化。
置顶功能:
小红旗(可以标注一个时间轴):
Perfetto 还可以通过插旗子的方法来在 Trace 上做标记,Perfetto 支持你把鼠标放到 Trace 最上面,就会出现一个旗子,点击左键即可插一个旗子在上面,方便我们标记某个事件发生,或者某个时间点。
Perfetto查看冷启动时间
冷启动时间:从手指点击桌面图标开始计算到显示app第一帧画面之间的时间差。
一般公司测试人员冷启动测试方法:高速摄像机(FPS高,误差小)。
主线程与渲染线程
主线程的创建
Android App 的进程是基于 Linux 的,其管理也是基于 Linux 的进程管理机制,所以其创建也是调用了 fork 函数。
frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
pid_t pid = fork();
Fork 出来的进程,我们这里可以把他看做主线程,但是这个线程还没有和 Android 进行连接,所以无法处理 Android App 的 Message ;由于 Android App 线程运行基于消息机制 ,那么这个 Fork 出来的主线程需要和 Android 的 Message 消息绑定,才能处理 Android App 的各种 Message。
这里就引入了 ActivityThread ,确切的说,ActivityThread 应该起名叫 ProcessThread 更贴切一些。ActivityThread 连接了 Fork 出来的进程和 App 的 Message ,他们的通力配合组成了我们熟知的 Android App 主线程。所以说 ActivityThread 其实并不是一个 Thread,而是他初始化了 Message 机制所需要的 MessageQueue、Looper、Handler ,而且其 Handler 负责处理大部分 Message 消息,所以我们习惯上觉得 ActivityThread 是主线程,其实他只是主线程的一个逻辑处理单元。
Activity Thread的创建
App 进程 fork 出来之后,回到 App 进程,查找 ActivityThread 的 Main函数。
com/android/internal/os/ZygoteInit.java
static final Runnable childZygoteInit(
int targetSdkVersion, String[] argv, ClassLoader classLoader) {
RuntimeInit.Arguments args = new RuntimeInit.Arguments(argv);
return RuntimeInit.findStaticMain(args.startClass, args.startArgs, classLoader);
}
}
这里的 startClass 就是 ActivityThread,找到之后调用,逻辑就到了 ActivityThread的main函数。
android/app/ActivityThread.java
public static void main(String[] args) {
//1. 初始化 Looper、MessageQueue
Looper.prepareMainLooper();
// 2. 初始化 ActivityThread
ActivityThread thread = new ActivityThread();
// 3. 主要是调用 AMS.attachApplicationLocked,同步进程信息,做一些初始化工作
thread.attach(false, startSeq);
// 4. 获取主线程的 Handler,这里是 H ,基本上 App 的 Message 都会在这个 Handler 里面进行处理
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// 5. 初始化完成,Looper 开始工作
Looper.loop();
}
main 函数处理完成之后,主线程就算是正式上线开始工作,其 Perfetto 流程如下:
Activity Thread的功能
另外我们经常说的,Android 四大组件都是运行在主线程上的,其实这里也很好理解,看一下 ActivityThread 的 Handler 的 Message 就知道了。
class H extends Handler { //摘抄了部分
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int STOP_SERVICE = 116;
public static final int BIND_SERVICE = 121;
public static final int UNBIND_SERVICE = 122;
public static final int DUMP_SERVICE = 123;
public static final int REMOVE_PROVIDER = 131;
public static final int DISPATCH_PACKAGE_BROADCAST = 133;
public static final int DUMP_PROVIDER = 141;
public static final int UNSTABLE_PROVIDER_DIED = 142;
public static final int INSTALL_PROVIDER = 145;
public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
}
可以看到,进程创建、Activity 启动、Service 的管理、Receiver 的管理、Provider 的管理这些都会在这里处理,然后进到具体的 handleXXX。
渲染线程的创建和发展
主线程讲完了我们来讲渲染线程,渲染线程也就是 RenderThread ,最初的 Android 版本里面是没有渲染线程的,渲染工作都是在主线程完成,使用的也都是 CPU ,调用的是 libSkia 这个库,RenderThread 是在 Android Lollipop 中新加入的组件,负责承担一部分之前主线程的渲染工作,减轻主线程的负担。
软件绘制
我们一般提到的硬件加速,指的就是 GPU 加速,这里可以理解为用 RenderThread 调用 GPU 来进行渲染加速 。 硬件加速在目前的 Android 中是默认开启的, 所以如果我们什么都不设置,那么我们的进程默认都会有主线程和渲染线程(有可见的内容)。我们如果在 App 的 AndroidManifest 里面,在 Application 标签里面加一个。
android:hardwareAccelerated="false"
我们就可以关闭硬件加速,系统检测到你这个 App 关闭了硬件加速,就不会初始化 RenderThread ,只有主线程,没有渲染线程,直接 cpu 调用 libSkia 来进行渲染。
主线程由于要进行渲染工作,所以执行的时间变长了,也更容易出现卡顿,同时帧与帧直接的空闲间隔也变短了,使得其他 Message 的执行时间被压缩。
硬件加速绘制
正常情况下,硬件加速是开启的,主线程的 draw 函数并没有真正的执行 drawCall ,而是把要 draw 的内容记录到 DIsplayList 里面,同步到 RenderThread 中,一旦同步完成,主线程就可以被释放出来做其他的事情,RenderThread 则继续进行渲染工作。
主线程和渲染线程一帧的工作流程(每一帧都会遵循这个流程,不过有的帧需要处理的事情多,有的帧需要处理的事情少) ,重点看 “UI Thread ” 和 RenderThread 这两行。
这张图对应的工作流程如下:
主线程处于 Sleep 状态,等待 Vsync 信号。
Vsync 信号到来,主线程被唤醒,Choreographer 回调 FrameDisplayEventReceiver.onVsync 开始一帧的绘制。
处理 App 这一帧的 Input 事件(如果有的话)。
处理 App 这一帧的 Animation 事件(如果有的话)。
处理 App 这一帧的 Traversal 事件(如果有的话)。
主线程与渲染线程同步渲染数据,同步结束后,主线程结束一帧的绘制,可以继续处理下一个 Message(如果有的话,IdleHandler 如果不为空,这时候也会触发处理),或者进入 Sleep 状态等待下一个 Vsync。
渲染线程首先需要从 BufferQueue 里面取一个 Buffer(dequeueBuffer) , 进行数据处理之后,调用 OpenGL 相关的函数,真正地进行渲染操作,然后将这个渲染好的 Buffer 还给 BufferQueue (queueBuffer) , SurfaceFlinger 在 Vsync-SF 到了之后,将所有准备好的 Buffer 取出进行合成(这个流程在下面的SurfaceFlinger会讲解)。
渲染线程初始化
渲染线程初始化在真正需要 draw 内容的时候,一般我们启动一个 Activity ,在第一个 draw 执行的时候,会去检测渲染线程是否初始化,如果没有则去进行初始化。
android/view/ViewRootImpl.java
mAttachInfo.mThreadedRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
在Perfetto表现如下:
后续直接调用 draw。
android/graphics/HardwareRenderer.java
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
choreographer.mFrameInfo.markDrawStart();
updateRootDisplayList(view, callbacks);
if (attachInfo.mPendingAnimatingRenderNodes != null) {
final int count = attachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
registerAnimatingRenderNode(
attachInfo.mPendingAnimatingRenderNodes.get(i));
}
attachInfo.mPendingAnimatingRenderNodes.clear();
attachInfo.mPendingAnimatingRenderNodes = null;
}
int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
setEnabled(false);
attachInfo.mViewRootImpl.mSurface.release();
attachInfo.mViewRootImpl.invalidate();
}
if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
attachInfo.mViewRootImpl.invalidate();
}
}
上面的 draw 只是更新 DIsplayList ,更新结束后,调用 syncAndDrawFrame ,通知渲染线程开始工作,主线程释放。渲染线程的核心实现在 libhwui 库里面,其代码位于 frameworks/base/libs/hwui。
frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
int RenderProxy::syncAndDrawFrame() {
return mDrawFrameTask.drawFrame();
}
其核心流程在 Perfetto 上的表现如下:
主线程和渲染线程的分工
主线程负责处理进程 Message、处理 Input 事件、处理 Animation 逻辑、处理 Measure、Layout、Draw ,更新 DIsplayList ,但是不涉及 SurfaceFlinger 打交道;渲染线程负责渲染渲染相关的工作,一部分工作也是 CPU 来完成的,一部分操作是调用 OpenGL 函数来完成的。
当启动硬件加速后,在 Measure、Layout、Draw 的 Draw 这个环节,Android 使用 DisplayList 进行绘制而非直接使用 CPU 绘制每一帧。DisplayList 是一系列绘制操作的记录,抽象为 RenderNode 类,这样间接的进行绘制操作的优点如下:
DisplayList 可以按需多次绘制而无须同业务逻辑交互。
特定的绘制操作(如 translation, scale 等)可以作用于整个 DisplayList 而无须重新分发绘制操作。
当知晓了所有绘制操作后,可以针对其进行优化:例如,所有的文本可以一起进行绘制一次。
可以将对 DisplayList 的处理转移至另一个线程(也就是 RenderThread)。
主线程在 sync 结束后可以处理其他的 Message,而不用等待 RenderThread 结束。
游戏的主线程与渲染线程
游戏大多使用单独的渲染线程 ,直接跟 SurfaceFlinger 进行交互,其主线程的存在感比较低,绝大部分的逻辑都是自己在自己的渲染线程里面实现的。大家可以看一下王者荣耀对应的 Perfetto ,一帧整体流程如下:
王者荣耀主线程的主要工作,就是把 Input 事件传给 Unity 的渲染线程,渲染线程收到 Input 事件之后,进行逻辑处理,画面更新等。
SurfaceFlinger讲解
SurfaceFlinger 的主要功能是接收缓冲区,创建缓冲区,并将缓冲区发送到显示器。WindowManager 为 SurfaceFlinger 提供了缓冲区和窗口元数据,SurfaceFlinger 使用这些缓冲区和窗口元数据来组合具有显示的表面。
详细定义可以看官网: SurfaceFlinger 的定义。
在Perfetto中,我们关注的重点就是上面这幅图对应的部分:
App 部分。
BufferQueue 部分。
SurfaceFlinger 部分。
HWComposer 部分。
这四部分,在 Perfetto 中都有可以对应的地方,以时间发生的顺序排序就是 1、2、3、4,下面我们从 Perfetto 的这四部分来看整个渲染的流程。
App部分
关于 App 部分,在上面的主线程渲染线程部分已经讲解过了,其主要的流程如下图:
从 SurfaceFlinger 的角度来看,App 部分主要负责生产 SurfaceFlinger 合成所需要的 Surface。
App 与 SurfaceFlinger 的交互主要集中在三点:
Vsync 信号的接收和处理。
RenderThread 的 dequeueBuffer。
RenderThread 的 queueBuffer。
Vsync 信号的接收和处理
App 和 SurfaceFlinger 的第一个交互点就是 Vsync 信号的请求和接收,Vsync-App 信号到达,应用收到这个信号后,开始一帧的渲染准备。
Triple Buffer
BufferQueue 部分
如下图,每个有显示界面的进程对应一个 BufferQueue,使用方创建并拥有 BufferQueue 数据结构,并且可存在于与其生产方不同的进程中,BufferQueue 工作流程如下:
上图主要是 dequeue、queue、acquire、release ,在这个例子里面,App 是生产者,负责填充显示缓冲区(Buffer);SurfaceFlinger 是消费者,将各个进程的显示缓冲区做合成操作:
dequeue(生产者发起) : 当生产者需要缓冲区时,它会通过调用 dequeueBuffer() 从 BufferQueue 请求一个可用的缓冲区,并指定缓冲区的宽度、高度、像素格式和使用标记。
queue(生产者发起):生产者填充缓冲区并通过调用 queueBuffer() 将缓冲区返回到队列。
acquire(消费者发起) :消费者通过 acquireBuffer() 获取该缓冲区并使用该缓冲区的内容。
release(消费者发起) :当消费者操作完成后,它会通过调用 releaseBuffer() 将该缓冲区返回到队列。
Single Buffer
单 Buffer 的情况下,因为只有一个 Buffer 可用,那么这个 Buffer 既要用来做合成显示,又要被应用拿去做渲染。
理想情况下,单 Buffer 是可以完成任务的(有 Vsync-Offset 存在的情况下)。
App 收到 Vsync 信号,获取 Buffer 开始渲染。
间隔 Vsync-Offset 时间后,SurfaceFlinger 收到 Vsync 信号,开始合成。
屏幕刷新,我们看到合成后的画面。
但是很不幸,理想情况我们也就想一想,这期间如果 App 渲染或者 SurfaceFlinger 合成在屏幕显示刷新之前还没有完成,那么屏幕刷新的时候,拿到的 Buffer 就是不完整的,在用户看来,就有种撕裂的感觉。
当然 Single Buffer 已经没有在使用,上面只是一个例子。
Double Buffer
Double Buffer 相当于 BufferQueue 中有两个 Buffer 可供轮转,消费者在消费 Buffer的同时,生产者也可以拿到备用的 Buffer 进行生产操作。
下面我们来看理想情况下,Double Buffer 的工作流程。
但是 Double Buffer 也会存在性能上的问题,比如下面的情况,App 连续两帧生产都超过 Vsync 周期(准确的说是错过 SurfaceFlinger 的合成时机) ,就会出现掉帧情况。
Triple Buffer
Triple Buffer 中,我们又加入了一个 BackBuffer ,这样的话 BufferQueue 里面就有三个 Buffer 可以轮转了,当 FrontBuffer 在被使用的时候,App 有两个空闲的 Buffer 可以拿去生产,就算生产过程中有 GPU 超时,CPU 任然可以拿到新的 Buffer 进行生产(即 SurfaceFling 消费 FrontBuffer,GPU 使用一个 BackBuffer,CPU使用一个 BackBuffer)。
下面就是引入 Triple Buffer 之后,解决了 Double Buffer 中遇到的由于 Buffer 不足引起的掉帧问题。
这里把两个图放到一起来看,方便大家做对比(一个是 Double Buffer 掉帧两次,一个是使用 Triple Buffer 只掉了一帧)。
Triple Buffer 的作用
缓解掉帧
从上一节 Double Buffer 和 Triple Buffer 的对比图可以看到,在这种情况下(出现连续主线程超时),三个 Buffer 的轮转有助于缓解掉帧出现的次数(从掉帧两次 -> 只掉帧一次)。
所以从第一节如何定义掉帧这里我们就知道,App 主线程超时不一定会导致掉帧,由于 Triple Buffer 的存在,部分 App 端的掉帧(主要是由于 GPU 导致),到 SurfaceFlinger 这里未必是掉帧,这是看 Perfetto 的时候需要注意的一个点。
减少主线程和渲染线程等待时间
双 Buffer 的轮转, App 主线程有时候必须要等待 SurfaceFlinger(消费者)释放 Buffer 后,才能获取 Buffer 进行生产,这时候就有个问题,现在大部分手机 SurfaceFlinger 和 App 同时收到 Vsync 信号,如果出现App 主线程等待 SurfaceFlinger(消费者)释放 Buffer ,那么势必会让 App 主线程的执行时间延后,比如下面这张图,可以明显看到:Buffer B 并不是在 Vsync 信号来的时候开始被消费(因为还在使用),而是等 Buffer A 被消费后,Buffer B 被释放,App 才能拿到 Buffer B 进行生产,这期间就有一定的延迟,会让主线程可用的时间变短。
降低 GPU 和 SurfaceFlinger 瓶颈
双 Buffer 的时候,App 生产的 Buffer 必须要及时拿去让 GPU 进行渲染,然后 SurfaceFlinger 才能进行合成,一旦 GPU 超时,就很容易出现SurfaceFlinger 无法及时合成而导致掉帧在三个 Buffer 轮转的时候,App 生产的 Buffer 可以及早进入 BufferQueue,让 GPU 去进行渲染(因为不需要等待,就算这里积累了 2 个 Buffer,下下一帧才去合成,这里也会提早进行,而不是在真正使用之前去匆忙让 GPU 去渲染),另外 SurfaceFlinger 本身的负载如果比较大,三个 Buffer 轮转也会有效降低 dequeueBuffer 的等待时间。
SurfaceFlinger 端的 Perfetto 如下所示:
SurfaceFlinger工作流程
SurfaceFlinger 的主要工作就是合成:
当 VSYNC 信号到达时,SurfaceFlinger 会遍历它的层列表,以寻找新的缓冲区。如果找到新的缓冲区,它会获取该缓冲区;否则,它会继续使用以前获取的缓冲区。SurfaceFlinger 必须始终显示内容,因此它会保留一个缓冲区。如果在某个层上没有提交缓冲区,则该层会被忽略。SurfaceFlinger 在收集可见层的所有缓冲区之后,便会询问 Hardware Composer 应如何进行合成。其 Perfetto 主线程可用看到其主要是在收到 Vsync 信号后开始工作:
SurfaceFlinger相关代码在下面,后续会专门写一篇SurfaceFlinger实现流程:
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
SurfaceFlinger启动流程图解
SurfaceFlinger 端判断掉帧
通常我们通过 Perfetto 判断应用是否掉帧的时候,一般是直接看 SurfaceFlinger 部分,主要是下面几个步骤:
1.SurfaceFlinger 的主线程在每个 Vsync-SF 的时候是否没有合成?
2.如果没有合成操作,那么需要看没有合成的原因:
因为 SurfaceFlinger 检查发现没有可用的 Buffer 而没有合成操作?
因为 SurfaceFlinger 被其他的工作占用(比如截图、HWC 等)?
3.如果有合成操作,那么需要看对应的 App 的 可用 Buffer 个数是否正常:如果 App 此时可用 Buffer 为 0,那么看 App 端为何没有及时 queueBuffer(这就一般是应用自身的问题了),因为 SurfaceFlinger 合成操作触发可能是其他的进程有可用的 Buffer。
下图为APP耗时导致的掉帧:
SurfaceFlinger 端可以看到 SurfaceFlinger 主线程和合成情况和应用对应的 BufferQueue 中 Buffer 的情况。如上图,就是一个掉帧的例子。App 没有及时渲染完成,且此时 BufferQueue 中也没有前几帧的 Buffer,所以这一帧 SurfaceFlinger 没有合成对应 App 的 Layer,在用户看来这里就掉了一帧。
从 App 端无法看出是否掉帧,对应的 SurfaceFlinger 的 Trace 如下, 可以看到由于有 Triple Buffer 的存在, SF 这里有之前 App 的 Buffer,所以尽管 App 超过了一帧的时间, 但是 SF 这里依然有可用来合成的 Buffer, 所以没有掉帧。
逻辑掉帧
上面的掉帧我们是从渲染这边来看的,这种掉帧在 Perfetto 中可以很容易就发现;还存在一种掉帧情况叫逻辑掉帧。
逻辑掉帧指的是由于应用自己的代码逻辑问题,导致画面更新的时候,不是以均匀或者物理曲线的方式,而是出现跳跃更新的情况,这种掉帧一般在 Perfetto 上没法看出来,但是用户在使用的时候可以明显感觉到。
举一个简单的例子,比如说列表滑动的时候,如果我们滑动松手后列表的每一帧前进步长是一个均匀变化的曲线,最后趋近于 0,这样就是完美的;但是如果出现这一帧相比上一帧走了 20,下一帧相比这一帧走了 10,下下一帧相比下一帧走了 30,这种就是跳跃更新,在 Perfetto 上每一帧都是及时渲染且 SurfaceFlinger 都及时合成的,但是用户用起来就是觉得会卡. 不过我列举的这个例子中,Android 已经针对这种情况做了优化,感兴趣的可以去看一下android/view/animation/AnimationUtils.java 这个类,重点看下面三个方法的使用。
public static void lockAnimationClock(long vsyncMillis);
public static void unlockAnimationClock();
public static long currentAnimationTimeMillis();
Android 系统的动画一般不会有这个问题,但是应用开发者就保不齐会写这种代码,比如做动画的时候根据当前的时间(而不是 Vsync 到来的时间)来计算动画属性变化的情况,这种情况下,一旦出现掉帧,动画的变化就会变得不均匀。
HWComposer 部分
Hardware Composer HAL (HWC) 用于确定通过可用硬件来合成缓冲区的最有效方法。作为 HAL,其实现是特定于设备的,而且通常由显示设备硬件原始设备制造商 (OEM) 完成。
当您考虑使用叠加平面时,很容易发现这种方法的好处,它会在显示硬件(而不是 GPU)中合成多个缓冲区。例如,假设有一部普通 Android 手机,其屏幕方向为纵向,状态栏在顶部,导航栏在底部,其他区域显示应用内容。每个层的内容都在单独的缓冲区中。您可以使用以下任一方法处理合成(后一种方法可以显著提高效率):
将应用内容渲染到暂存缓冲区中,然后在其上渲染状态栏,再在其上渲染导航栏,最后将暂存缓冲区传送到显示硬件。
将三个缓冲区全部传送到显示硬件,并指示它从不同的缓冲区读取屏幕不同部分的数据。
显示处理器功能差异很大。叠加层的数量(无论层是否可以旋转或混合)以及对定位和叠加的限制很难通过 API 表达。为了适应这些选项,HWC 会执行以下计算(由于硬件供应商可以定制决策代码,因此可以在每台设备上实现最佳性能):
SurfaceFlinger 向 HWC 提供一个完整的层列表,并询问“您希望如何处理这些层?”
HWC 的响应方式是将每个层标记为叠加层或 GLES 合成。
SurfaceFlinger 会处理所有 GLES 合成,将输出缓冲区传送到 HWC,并让 HWC 处理其余部分。
当屏幕上的内容没有变化时,叠加平面的效率可能会低于 GL 合成。当叠加层内容具有透明像素且叠加层混合在一起时,尤其如此。在此类情况下,HWC 可以选择为部分或全部层请求 GLES 合成,并保留合成的缓冲区。如果 SurfaceFlinger 返回来要求合成同一组缓冲区,HWC 可以继续显示先前合成的暂存缓冲区。这可以延长闲置设备的电池续航时间。
运行 Android 4.4 或更高版本的设备通常支持 4 个叠加平面。尝试合成的层数多于叠加层数会导致系统对其中一些层使用 GLES 合成,这意味着应用使用的层数会对能耗和性能产生重大影响。
Choreographer讲解
Choreographer 的引入,主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机. 目前大部分手机都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 Vsync 的周期也设置为 16.6 ms,每个 16.6 ms , Vsync 信号唤醒 Choreographer 来做 App 的绘制操作 ,这就是引入 Choreographer 的主要作用. 了解 Choreographer 还可以帮助 App 开发者知道程序每一帧运行的基本原理,也可以加深对 Message、Handler、Looper、MessageQueue、Measure、Layout、Draw 的理解。
主线程运行机制的本质
在讲 Choreographer 之前,我们先理一下 Android 主线程运行的本质,其实就是 Message 的处理过程,我们的各种操作,包括每一帧的渲染操作 ,都是通过 Message 的形式发给主线程的 MessageQueue ,MessageQueue 处理完消息继续等下一个消息,MethodTrace 如下图所示:
引入Choreographer前
引入 Vsync 之前的 Android 版本,渲染一帧相关的 Message ,中间是没有间隔的,上一帧绘制完,下一帧的 Message 紧接着就开始被处理。这样的问题就是,帧率不稳定,可能高也可能低,不稳定,MethodTrace 如下图所示:
对于用户来说,稳定的帧率才是好的体验,比如你玩王者荣耀,相比 fps 在 60 和 40 之间频繁变化,用户感觉更好的是稳定在 50 fps 的情况。
所以 Android 的演进中,引入了 Vsync + TripleBuffer + Choreographer 的机制,其主要目的就是提供一个稳定的帧率输出机制,让软件层和硬件层可以以共同的频率一起工作。
引入 Choreographer后
Choreographer 的引入,主要是配合 Vsync ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 Vsync 到来的时候 ,系统通过对 Vsync 信号周期的调整,来控制每一帧绘制操作的时机 ,Vsync 信号到来唤醒 Choreographer 来做 App 的绘制操作 ,如果每个 Vsync 周期应用都能渲染完成,给用户的感觉就是非常流畅,这就是引入 Choreographer 的主要作用。
上面主要是以60HZ为例,当然目前使用 90Hz 刷新率屏幕的手机越来越多,Vsync 周期从 16.6ms 到了 11.1ms,上图中的操作要在更短的时间内完成,对性能的要求也越来越高。
Choreographer 作用
Choreographer 扮演 Android 渲染链路中承上启下的角色:
承上:负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等。
启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FrameDisplayEventReceiver.onVsync );请求 Vsync(FrameDisplayEventReceiver.scheduleVsync)。
从上面可以看出来, Choreographer 担任的是一个工具人的角色,他之所以重要,是因为通过 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 这一套从上到下的机制,保证了 Android App 可以以一个稳定的帧率运行(20fps、90fps 或者 60fps),减少帧率波动带来的不适感。
了解 Choreographer 还可以帮助 App 开发者知道程序每一帧运行的基本原理,也可以加深对 Message、Handler、Looper、MessageQueue、Input、Animation、Measure、Layout、Draw 的理解 , 很多 APM 工具也用到了 Choreographer( 利用 FrameCallback + FrameInfo ) + MessageQueue ( 利用 IdleHandler ) + Looper ( 设置自定义 MessageLogging) 这些组合拳,深入了解了这些之后,再去做优化,脑子里的思路会更清晰。
另外虽然画图是一个比较好的解释流程的好路子,但是我个人不是很喜欢画图,因为平时 Perfetto 和 MethodTrace 用的比较多,Perfetto 是按从左到右展示整个系统的运行情况的一个工具(包括 cpu、SurfaceFlinger、SystemServer、App 等关键进程),使用 Perfetto 和 MethodTrace 也可以很方便地展示关键流程。当你对系统代码比较熟悉的时候,看 Perfetto 就可以和手机运行的实际情况对应起来。所以下面的文章除了一些网图之外,其他的我会多以 Perfetto 来展示。
从 Perfetto 的角度来看 Choreogrepher 的工作流程
下图以滑动推特APP为例子,我们先看一下从左到右滑动桌面的一个完整的预览图(App 进程),可以看到 Perfetto 中从左到右,每一个绿色的帧都表示一帧,表示最终我们可以手机上看到的画面:
图中每一个紫色的条宽度是一个 Vsync 的时间,这个是120HZ也就是 8.3ms。
每一帧处理的流程:接收到 Vsync 信号回调-> UI Thread –> RenderThread –> SurfaceFlinger(图中未显示)。
UI Thread 和 RenderThread 就可以完成 App 一帧的渲染,渲染完的 Buffer 抛给 SurfaceFlinger 去合成,然后我们就可以在屏幕上看到这一帧了。
可以看到推特滑动的每一帧耗时都很短(Ui Thread 耗时 + RenderThread 耗时),但是由于 Vsync 的存在,每一帧都会等到 Vsync 才会去做处理。
有了上面这个整体的概念,我们将 UI Thread 的每一帧放大来看,看看 Choreogrepher 的位置以及 Choreogrepher 是怎么组织每一帧的。
Choreographer 的工作流程
Choreographer 初始化
初始化 FrameHandler ,绑定 Looper。
初始化 FrameDisplayEventReceiver ,与 SurfaceFlinger 建立通信用于接收和请求 Vsync。
初始化 CallBackQueues。
SurfaceFlinger 的 appEventThread 唤醒发送 Vsync ,Choreographer 回调 FrameDisplayEventReceiver.onVsync , 进入 Choreographer 的主处理函数 doFrame。
Choreographer.doFrame 计算掉帧逻辑。
Choreographer.doFrame 处理 Choreographer 的第一个 callback : input。
Choreographer.doFrame 处理 Choreographer 的第二个 callback : animation。
Choreographer.doFrame 处理 Choreographer 的第三个 callback : insets animation。
Choreographer.doFrame 处理 Choreographer 的第四个 callback : traversal。
traversal-draw 中 UIThread 与 RenderThread 同步数据。
Choreographer.doFrame 处理 Choreographer 的第五个 callback : commit ?
RenderThread 处理绘制命令,将处理好的绘制命令发给 GPU 处理。
调用 swapBuffer 提交给 SurfaceFlinger 进行合成(此时 Buffer 并没有真正完成,需要等 CPU 完成后 SurfaceFlinger 才能真正使用,新版本的 Perfetto 中有 gpu 的 fence 来标识这个时间)。
第一步初始化完成后,后续就会在步骤 2-9 之间循环
同时也附上这一帧所对应的 MethodTrace(这里预览一下即可,下面会有详细的大图)。
下面我们就从源码的角度,来看一下具体的实现。
源码解析
下面从源码的角度来简单看一下,源码只摘抄了部分重要的逻辑,其他的逻辑则被剔除,另外 Native 部分与 SurfaceFlinger 交互的部分也没有列入,不是本文的重点,有兴趣的可以自己去跟一下。
Choreographer 的初始化
Choreographer 的单例初始化
// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance = new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
// 获取当前线程的 Looper
Looper looper = Looper.myLooper();
......
// 构造 Choreographer 对象
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
Choreographer 的构造函数
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// 1. 初始化 FrameHandler
mHandler = new FrameHandler(looper);
// 2. 初始化 DisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
//3. 初始化 CallbacksQueues
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
......
}
FrameHandler
private final class FrameHandler extends Handler {
......
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME://开始渲染下一帧的操作
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC://请求 Vsync
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK://处理 Callback
doScheduleCallback(msg.arg1);
break;
}
}
}
Choreographer 初始化链
在 Activity 启动过程,执行完 onResume 后,会调用 Activity.makeVisible(),然后再调用到 addView(), 层层调用会进入如下方法。
ActivityThread.handleResumeActivity(IBinder, boolean, boolean, String) (android.app)
-->WindowManagerImpl.addView(View, LayoutParams) (android.view)
-->WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view)
-->ViewRootImpl.ViewRootImpl(Context, Display) (android.view)
public ViewRootImpl(Context context, Display display) {
......
mChoreographer = Choreographer.getInstance();
......
}
FrameDisplayEventReceiver
Vsync 的注册、申请、接收都是通过 FrameDisplayEventReceiver 这个类,所以可以先简单介绍一下。 FrameDisplayEventReceiver 继承 DisplayEventReceiver , 有三个比较重要的方法:
onVsync – Vsync 信号回调。
run – 执行 doFrame。
scheduleVsync – 请求 Vsync 信号。
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
......
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
public void scheduleVsync() {
......
nativeScheduleVsync(mReceiverPtr);
......
}
}
Choreographer 中 Vsync 的注册
从下面的函数调用栈可以看到,Choreographer 的内部类 FrameDisplayEventReceiver.onVsync 负责接收 Vsync 回调,通知 UIThread 进行数据处理。
那么 FrameDisplayEventReceiver 是通过什么方式在 Vsync 信号到来的时候回调 onVsync 呢?答案是 FrameDisplayEventReceiver 的初始化的时候,最终通过监听文件句柄的形式,其对应的初始化流程如下:
android/view/Choreographer.java
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null;
......
}
android/view/Choreographer.java
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
android/view/DisplayEventReceiver.java
public DisplayEventReceiver(Looper looper, int vsyncSource) {
......
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue, vsyncSource);
}
简单来说,FrameDisplayEventReceiver 的初始化过程中,通过 BitTube(本质是一个 socket pair),来传递和请求 Vsync 事件,当 SurfaceFlinger 收到 Vsync 事件之后,通过 appEventThread 将这个事件通过 BitTube 传给 DisplayEventDispatcher ,DisplayEventDispatcher 通过 BitTube 的接收端监听到 Vsync 事件之后,回调 Choreographer.FrameDisplayEventReceiver.onVsync ,触发开始一帧的绘制,如下图:
Choreographer 处理一帧的逻辑
Choreographer 处理绘制的逻辑核心在 Choreographer.doFrame 函数中,从下图可以看到,FrameDisplayEventReceiver.onVsync post 了自己,其 run 方法直接调用了 doFrame 开始一帧的逻辑处理。
android/view/Choreographer.java
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
doFrame 函数主要做下面几件事:
计算掉帧逻辑。
记录帧绘制信息。
执行 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_INSETS_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT。
计算掉帧逻辑
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
......
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread.");
}
}
......
}
......
}
Choreographer.doFrame 的掉帧检测比较简单,从下图可以看到,Vsync 信号到来的时候会标记一个 start_time ,执行 doFrame 的时候标记一个 end_time ,这两个时间差就是 Vsync 处理时延,也就是掉帧。
记录帧绘制信息
Choreographer 中 FrameInfo 来负责记录帧的绘制信息,doFrame 执行的时候,会把每一个关键节点的绘制时间记录下来,我们使用 dumpsys gfxinfo 就可以看到。当然 Choreographer 只是记录了一部分,剩余的部分在 hwui 那边来记录。
从 FrameInfo 这些标志就可以看出记录的内容,后面我们看 dumpsys gfxinfo 的时候数据就是按照这个来排列的。
// Various flags set to provide extra metadata about the current frame
private static final int FLAGS = 0;
// Is this the first-draw following a window layout?
public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
// A renderer associated with just a Surface, not with a ViewRootImpl instance.
public static final long FLAG_SURFACE_CANVAS = 1 << 2;
@LongDef(flag = true, value = {
FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS })
@Retention(RetentionPolicy.SOURCE)
public @interface FrameInfoFlags {}
// The intended vsync time, unadjusted by jitter
private static final int INTENDED_VSYNC = 1;
// Jitter-adjusted vsync time, this is what was used as input into the
// animation & drawing system
private static final int VSYNC = 2;
// The time of the oldest input event
private static final int OLDEST_INPUT_EVENT = 3;
// The time of the newest input event
private static final int NEWEST_INPUT_EVENT = 4;
// When input event handling started
private static final int HANDLE_INPUT_START = 5;
// When animation evaluations started
private static final int ANIMATION_START = 6;
// When ViewRootImpl#performTraversals() started
private static final int PERFORM_TRAVERSALS_START = 7;
// When View:draw() started
private static final int DRAW_START = 8;
doFrame 函数记录从 Vsync time 到 markPerformTraversalsStart 的时间。
void doFrame(long frameTimeNanos, int frame) {
......
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
// 处理 CALLBACK_INPUT Callbacks
mFrameInfo.markInputHandlingStart();
// 处理 CALLBACK_ANIMATION Callbacks
mFrameInfo.markAnimationsStart();
// 处理 CALLBACK_INSETS_ANIMATION Callbacks
// 处理 CALLBACK_TRAVERSAL Callbacks
mFrameInfo.markPerformTraversalsStart();
// 处理 CALLBACK_COMMIT Callbacks
......
}
执行 Callbacks
void doFrame(long frameTimeNanos, int frame) {
......
// 处理 CALLBACK_INPUT Callbacks
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
// 处理 CALLBACK_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 处理 CALLBACK_INSETS_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
// 处理 CALLBACK_TRAVERSAL Callbacks
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 处理 CALLBACK_COMMIT Callbacks
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
......
}
Input 回调调用栈
input callback 一般是执行 ViewRootImpl.ConsumeBatchedInputRunnable。
android/view/ViewRootImpl.java
final class ConsumeBatchedInputRunnable implements Runnable {
@Override
public void run() {
doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
}
}
void doConsumeBatchedInput(long frameTimeNanos) {
if (mConsumeBatchedInputScheduled) {
mConsumeBatchedInputScheduled = false;
if (mInputEventReceiver != null) {
if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos) && frameTimeNanos != -1) {
scheduleConsumeBatchedInput();
}
}
doProcessInputEvents();
}
}
Input 时间经过处理,最终会传给 DecorView 的 dispatchTouchEvent,这就到了我们熟悉的 Input 事件分发。
Animation 回调调用栈
一般我们接触的多的是调用 View.postOnAnimation 的时候,会使用到 CALLBACK_ANIMATION。
public void postOnAnimation(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.mChoreographer.postCallback(
Choreographer.CALLBACK_ANIMATION, action, null);
} else {
// Postpone the runnable until we know
// on which thread it needs to run.
getRunQueue().post(action);
}
}
那么一般是什么时候回调用到 View.postOnAnimation 呢,我截取了一张图,大家可以自己去看一下,接触最多的应该是 startScroll,Fling 这种操作。
其调用栈根据其 post 的内容,下面是桌面滑动松手之后的 fling 动画。
另外我们的 Choreographer 的 FrameCallback 也是用的 CALLBACK_ANIMATION。
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
Traversal 调用栈
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//为了提高优先级,先 postSyncBarrier
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 真正开始执行 measure、layout、draw
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 这里把 SyncBarrier remove
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 真正开始
performTraversals();
}
}
private void performTraversals() {
// measure 操作
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// layout 操作
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
// draw 操作
if (!cancelDraw && !newSurface) {
performDraw();
}
}
doTraversal 的 TraceView 示例
下一帧的 Vsync 请求
由于动画、滑动、Fling 这些操作的存在,我们需要一个连续的、稳定的帧率输出机制。这就涉及到了 Vsync 的请求逻辑,在连续的操作,比如动画、滑动、Fling 这些情况下,每一帧的 doFrame 的时候,都会根据情况触发下一个 Vsync 的申请,这样我们就可以获得连续的 Vsync 信号。看下面的 scheduleTraversals 调用栈(scheduleTraversals 中会触发 Vsync 请求)。
我们比较熟悉的 invalidate 和 requestLayout 都会触发 Vsync 信号请求。
我们下面以 Animation 为例,看看 Animation 是如何驱动下一个 Vsync ,来持续更新画面的。
ObjectAnimator 动画驱动逻辑
android/animation/ObjectAnimator.java
public void start() {
super.start();
}
android/animation/ValueAnimator.java
private void start(boolean playBackwards) {
......
addAnimationCallback(0); // 动画 start 的时候添加 Animation Callback
......
}
private void addAnimationCallback(long delay) {
......
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
android/animation/AnimationHandler.java
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
// post FrameCallback
getProvider().postFrameCallback(mFrameCallback);
}
......
}
// 这里的 mFrameCallback 回调 doFrame,里面 post了自己
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
// post 自己
getProvider().postFrameCallback(this);
}
}
};
调用 postFrameCallback 会走到 mChoreographer.postFrameCallback ,这里就会触发 Choreographer 的 Vsync 请求逻辑。
android/animation/AnimationHandler.java
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
android/view/Choreographer.java
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
// 请求 Vsync scheduleFrameLocked ->scheduleVsyncLocked-> mDisplayEventReceiver.scheduleVsync ->nativeScheduleVsync
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
通过上面的 Animation.start 设置,利用了 Choreographer.FrameCallback 接口,每一帧都去请求下一个 Vsync。动画过程中一帧的 TraceView 示例:
源码小结
Choreographer 是线程单例的,而且必须要和一个 Looper 绑定,因为其内部有一个 Handler 需要和 Looper 绑定,一般是 App 主线程的 Looper 绑定。
DisplayEventReceiver 是一个 abstract class,其 JNI 的代码部分会创建一个IDisplayEventConnection 的 Vsync 监听者对象。这样,来自 AppEventThread 的 VSYNC 中断信号就可以传递给 Choreographer 对象了。当 Vsync 信号到来时,DisplayEventReceiver 的 onVsync 函数将被调用。
DisplayEventReceiver 还有一个 scheduleVsync 函数。当应用需要绘制UI时,将首先申请一次 Vsync 中断,然后再在中断处理的 onVsync 函数去进行绘制。
Choreographer 定义了一个 FrameCallback interface,每当 Vsync 到来时,其 doFrame 函数将被调用。这个接口对 Android Animation 的实现起了很大的帮助作用。以前都是自己控制时间,现在终于有了固定的时间中断。
Choreographer 的主要功能是,当收到 Vsync 信号时,去调用使用者通过 postCallback 设置的回调函数。目前一共定义了五种类型的回调,它们分别是:
CALLBACK_INPUT : 处理输入事件处理有关。
CALLBACK_ANIMATION : 处理 Animation 的处理有关。
CALLBACK_INSETS_ANIMATION : 处理 Insets Animation 的相关回调。
CALLBACK_TRAVERSAL : 处理和 UI 等控件绘制有关。
CALLBACK_COMMIT : 处理 Commit 相关回调,主要是是用于执行组件 Application/Activity/Service 的 onTrimMemory,在 ApplicationThread 的 scheduleTrimMemory 方法中向 Choreographer 插入的;另外这个 Callback 也提供了一个监测一帧耗时的时机。
ListView 的 Item 初始化(obtain\setup) 会在 input 里面也会在 animation 里面,这取决于CALLBACK_INPUT 、CALLBACK_ANIMATION 会修改 view 的属性,所以要先与 CALLBACK_TRAVERSAL 执行。
APM 与 Choreographer
由于 Choreographer 的位置,许多性能监控的手段都是利用 Choreographer 来做的,除了自带的掉帧计算,Choreographer 提供的 FrameCallback 和 FrameInfo 都给 App 暴露了接口,让 App 开发者可以通过这些方法监控自身 App 的性能,其中常用的方法如下:
利用 FrameCallback 的 doFrame 回调。
利用 FrameInfo 进行监控。
使用 :adb shell dumpsys gfxinfo framestats
示例 :adb shell dumpsys gfxinfo com.meizu.flyme.launcher framestats
利用 SurfaceFlinger 进行监控。
使用 :adb shell dumpsys SurfaceFlinger –latency
示例 :adb shell dumpsys SurfaceFlinger –latency com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher#0
利用 SurfaceFlinger PageFlip 机制进行监控。
使用 : adb service call SurfaceFlinger 1013
备注:需要系统权限
Choreographer 自身的掉帧计算逻辑。
BlockCanary 基于 Looper 的性能监控。
利用 FrameCallback 的 doFrame 回调
FrameCallback 接口
public interface FrameCallback {
public void doFrame(long frameTimeNanos);
}
接口使用
Choreographer.getInstance().postFrameCallback(youOwnFrameCallback );
接口处理
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
......
postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
利用 FrameInfo 进行监控
adb shell dumpsys gfxinfo framestats
Window: StatusBar
Stats since: 17990256398ns
Total frames rendered: 1562
Janky frames: 361 (23.11%)
50th percentile: 6ms
90th percentile: 23ms
95th percentile: 36ms
99th percentile: 101ms
Number Missed Vsync: 33
Number High input latency: 683
Number Slow UI thread: 273
Number Slow bitmap uploads: 8
Number Slow issue draw commands: 18
Number Frame deadline missed: 287
HISTOGRAM: 5ms=670 6ms=128 7ms=84 8ms=63 9ms=38 10ms=23 11ms=21 12ms=20 13ms=25 14ms=39 15ms=65 16ms=36 17ms=51 18ms=37 19ms=41 20ms=20 21ms=19 22ms=18 23ms=15 24ms=14 25ms=8 26ms=4 27ms=6 28ms=3 29ms=4 30ms=2 31ms=2 32ms=6 34ms=12 36ms=10 38ms=9 40ms=3 42ms=4 44ms=5 46ms=8 48ms=6 53ms=6 57ms=4 61ms=1 65ms=0 69ms=2 73ms=2 77ms=3 81ms=4 85ms=1 89ms=2 93ms=0 97ms=2 101ms=1 105ms=1 109ms=1 113ms=1 117ms=1 121ms=2 125ms=1 129ms=0 133ms=1 150ms=2 200ms=3 250ms=0 300ms=1 350ms=1 400ms=0 450ms=0 500ms=0 550ms=0 600ms=0 650ms=0
---PROFILEDATA---
Flags,IntendedVsync,Vsync,OldestInputEvent,NewestInputEvent,HandleInputStart,AnimationStart,PerformTraversalsStart,DrawStart,SyncQueued,SyncStart,IssueDrawCommandsStart,SwapBuffers,FrameCompleted,DequeueBufferDuration,QueueBufferDuration,
0,10158314881426,10158314881426,9223372036854775807,0,10158315693363,10158315760759,10158315769821,10158316032165,10158316627842,10158316838988,10158318055915,10158320387269,10158321770654,428000,773000,
0,10158332036261,10158332036261,9223372036854775807,0,10158332799196,10158332868519,10158332877269,10158333137738,10158333780654,10158333993206,10158335078467,10158337689561,10158339307061,474000,885000,
0,10158348665353,10158348665353,9223372036854775807,0,10158349710238,10158349773102,10158349780863,10158350405863,10158351135967,10158351360446,10158352300863,10158354305654,10158355814509,471000,836000,
0,10158365296729,10158365296729,9223372036854775807,0,10158365782373,10158365821019,10158365825238,10158365975290,10158366547946,10158366687217,10158367240706,10158368429248,10158369291852,269000,476000,
利用 SurfaceFlinger 进行监控
命令解释:
数据的单位是纳秒,时间是以开机时间为起始点。
每一次的命令都会得到128行的帧相关的数据。
数据:
第一行数据,表示刷新的时间间隔refresh_period。
第1列:这一部分的数据表示应用程序绘制图像的时间点。
第2列:在SF(软件)将帧提交给H/W(硬件)绘制之前的垂直同步时间,也就是每帧绘制完提交到硬件的时间戳,该列就是垂直同步的时间戳。
第3列:在SF将帧提交给H/W的时间点,算是H/W接受完SF发来数据的时间点,绘制完成的时间点。
掉帧 jank 计算
每一行都可以通过下面的公式得到一个值,该值是一个标准,我们称为jankflag,如果当前行的jankflag与上一行的jankflag发生改变,那么就叫掉帧。
ceil((C - A) / refresh-period)
利用 SurfaceFlinger PageFlip 机制进行监控
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
mFlinger.transact(1013, data, reply, 0);
final int pageFlipCount = reply.readInt();
final long now = System.nanoTime();
final int frames = pageFlipCount - mLastPageFlipCount;
final long duration = now - mLastUpdateTime;
mFps = (float) (frames * 1e9 / duration);
mLastPageFlipCount = pageFlipCount;
mLastUpdateTime = now;
reply.recycle();
data.recycle();
Choreographer 自身的掉帧计算逻辑
SKIPPED_FRAME_WARNING_LIMIT 默认为30 , 由 debug.choreographer.skipwarning 这个属性控制。
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread.");
}
}
BlockCanary
Blockcanary 做性能监控使用的是 Looper 的消息机制,通过对 MessageQueue 中每一个 Message 的前后进行记录,打到监控性能的目的。
android/os/Looper.java
public static void loop() {
...
for (;;) {
...
// This must be in a local variable, in case a UI event sets the logger
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);
}
...
}
}
MessageQueue 与 Choreographer
所谓的异步消息其实就是这样的,我们可以通过 enqueueBarrier 往消息队列中插入一个 Barrier,那么队列中执行时间在这个 Barrier 以后的同步消息都会被这个 Barrier 拦截住无法执行,直到我们调用 removeBarrier 移除了这个 Barrier,而异步消息则没有影响,消息默认就是同步消息,除非我们调用了 Message 的 setAsynchronous,这个方法是隐藏的。只有在初始化 Handler 时通过参数指定往这个 Handler 发送的消息都是异步的,这样在 Handler 的 enqueueMessage 中就会调用 Message 的 setAsynchronous 设置消息是异步的,从上面 Handler.enqueueMessage 的代码中可以看到。
所谓异步消息,其实只有一个作用,就是在设置 Barrier 时仍可以不受 Barrier 的影响被正常处理,如果没有设置 Barrier,异步消息就与同步消息没有区别,可以通过 removeSyncBarrier 移除 Barrier。
SyncBarrier 在 Choreographer 中使用的一个示例
scheduleTraversals 的时候 postSyncBarrier
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//为了提高优先级,先 postSyncBarrier
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
doTraversal 的时候 removeSyncBarrier
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 这里把 SyncBarrier remove
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 真正开始
performTraversals();
}
}
Choreographer post Message 的时候,会把这些消息设为 Asynchronous ,这样 Choreographer 中的这些 Message 的优先级就会比较高。
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
SystemServer
窗口动画
Systrace 中的 SystemServer 一个比较重要的地方就是窗口动画,由于窗口归 SystemServer 来管,那么窗口动画也就是由 SystemServer 来进行统一的处理,其中涉及到两个比较重要的线程,Android.Anim 和 Android.Anim.if 这两个线程。
ActivityManagerService
AMS 和 WMS 算是 SystemServer 中最繁忙的两个 Service 了,与 AMS 相关的 Trace 一般会用 TRACE_TAG_ACTIVITY_MANAGER 这个 TAG,在 Systrace 中的名字是 ActivityManager。下面是启动一个新的进程的时候,AMS 的输出:
在进程和四大组件的各种场景一般都会有对应的 Trace 点来记录,比如大家熟悉的 ActivityStart、ActivityResume、activityStop 等,这些 Trace 点有一些在应用进程,有一些在 SystemServer 进程,所以大家在看 Activity 相关的代码逻辑的时候,需要不断在这两个进程之间进行切换,这样才能从一个整体的角度来看应用的状态变化和 SystemServer 在其中起到的作用。
WindowManagerService
与 WMS 相关的 Trace 一般会用 TRACE_TAG_WINDOW_MANAGER 这个 TAG,在 Systrace 中 WindowManagerService 在 SystemServer 中多在对应的 Binder 中出现,比如下面应用启动的时候,relayoutWindow 的 Trace 输出。
在 Window 的各种场景一般都会有对应的 Trace 点来记录,比如大家熟悉的 relayoutWIndow、performLayout、prepareToDisplay 等。
Input
input在Perfetto的实现流程
下面这张图是一个概览图,以滑动推特app为例 (滑动应用包括一个 Input_Down 事件 + 若干个 Input_Move 事件 + 一个 Input_Up 事件,这些事件和事件流都会在 Perfetto 上有所体现,这也是我们分析 Perfetto 的一个重要的切入点),主要牵扯到的模块是 SystemServer 和 App 模块,下图是他们各个区域在Perfetto上表现。
放大后Input在Perfetto上的表现。
InputReader 和 InputDispatcher 是跑在 SystemServer 里面的两个 Native 线程,负责读取和分发 Input 事件,我们分析 Perfetto 的 Input 事件流,首先是找到这里。下面针对上图中标号进行简单说明:
触摸屏每隔几毫秒扫描一次,如果有触摸事件,那么把事件上报到对应的驱动。
InputReader 负责从 EventHub 里面把 Input 事件读取出来,放到 InboundQueue 中然后交给 InputDispatcher 进行事件分发。
InputDispatcher 从 InboundQueue 中取出 Input 事件进行包装然后派发到各个 App(连接) 的 OutBoundQueue ,同时将事件记录到各个 App(连接) 的 WaitQueue。
App 接收到 Input 事件,同时记录到 PendingInputEventQueue ,然后对事件进行分发处理。
App 处理完成后,回调 InputManagerService 将负责监听的 WaitQueue 中对应的 Input 移除。
注:
OutboundQueue 里面放的是即将要被派发给对应 AppConnection 的事件。
WaitQueue 里面记录的是已经派发给 AppConnection 但是 App 还在处理没有返回处理成功的事件。
deliverInputEvent 标识 App UI Thread 被 Input 事件唤醒。
PendingInputEventQueue 里面记录的是 App 需要处理的 Input 事件,这里可以看到已经到了应用进程 。
通过上面的流程,一次 Input 事件就被消耗掉了(当然这只是正常情况,还有很多异常情况、细节处理,这里就不细说了,自己看相关流程的时候可以深挖一下)。
InputReader
InputReader 是一个 Native 线程,跑在 SystemServer 进程里面,其核心功能是从 EventHub 读取事件、进行加工、将加工好的事件发送到 InputDispatcher。
InputReader Loop 流程如下:
getEvents:通过 EventHub (监听目录 /dev/input )读取事件放入 mEventBuffer ,而mEventBuffer 是一个大小为256的数组, 再将事件 input_event 转换为 RawEvent。
processEventsLocked: 对事件进行加工, 转换 RawEvent -> NotifyKeyArgs(NotifyArgs)。
QueuedListener->flush:将事件发送到 InputDispatcher 线程, 转换 NotifyKeyArgs -> KeyEntry(EventEntry)。
核心代码 loopOnce 处理流程如下:
InputReader 核心 Loop 函数 loopOnce 逻辑如下:
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
{ // acquire lock
......
//获取输入事件、设备增删事件,count 为事件数量
size_t count = mEventHub ->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{
......
if (count) {//处理事件
processEventsLocked(mEventBuffer, count);
}
}
......
mQueuedListener->flush();//将事件传到 InputDispatcher,这里getListener 得到的就是 InputDispatcher
}
InputDispatcher
上面的 InputReader 调用 mQueuedListener->flush 之后 ,将 Input 事件加入到InputDispatcher 的 mInboundQueue ,然后唤醒 InputDispatcher。
InputDispatcher 的核心逻辑如下:
dispatchOnceInnerLocked(): 从 InputDispatcher 的 mInboundQueue 队列,取出事件 EventEntry。另外该方法开始执行的时间点 (currentTime) 便是后续事件 dispatchEntry 的分发时间 (deliveryTime)。
dispatchKeyLocked():满足一定条件时会添加命令 doInterceptKeyBeforeDispatchingLockedInterruptible。
enqueueDispatchEntryLocked():生成事件 DispatchEntry 并加入 connection 的 outbound 队列。
startDispatchCycleLocked():从 outboundQueue 中取出事件 DispatchEntry, 重新放入 connection 的 waitQueue 队列。
InputChannel.sendMessage 通过 socket 方式将消息发送给远程进程。
runCommandsLockedInterruptible():通过循环遍历的方式,依次处理 mCommandQueue 队列中的所有命令。而 mCommandQueue 队列中的命令是通过 postCommandLocked() 方式向该队列添加的。
其核心处理逻辑在 dispatchOnceInnerLocked 这里:
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// Ready to start a new event.
// If we don't already have a pending event, go grab one.
if (! mPendingEvent) {
if (mInboundQueue.isEmpty()) {
} else {
// Inbound queue has at least one entry.
mPendingEvent = mInboundQueue.dequeueAtHead();
traceInboundQueueLengthLocked();
}
// Poke user activity for this event.
if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
pokeUserActivityLocked(mPendingEvent);
}
// Get ready to dispatch the event.
resetANRTimeoutsLocked();
}
case EventEntry::TYPE_MOTION: {
done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
break;
}
if (done) {
if (dropReason != DROP_REASON_NOT_DROPPED) {
dropInboundEventLocked(mPendingEvent, dropReason);
}
mLastDropReason = dropReason;
releasePendingEventLocked();
*nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
}
}
InboundQueue
InputDispatcher 执行 notifyKey 的时候,会将 Input 事件封装后放到 InboundQueue 中,后续 InputDispatcher 循环处理 Input 事件的时候,就是从 InboundQueue 取出事件然后做处理。
OutboundQueue
Outbound 意思是出站,这里的 OutboundQueue 指的是要被 App 拿去处理的事件队列,每一个 App(Connection) 都对应有一个 OutboundQueue ,从 InboundQueue 那一节的图来看,事件会先进入 InboundQueue ,然后被 InputDIspatcher 派发到各个 App 的 OutboundQueue。
waitQueue
当 InputDispatcher 将 Input 事件分发出去之后,将 DispatchEntry 从 outboundQueue 中取出来放到 WaitQueue 中,当 publish 出去的事件被处理完成(finished),InputManagerService 就会从应用中得到一个回复,此时就会取出 WaitQueue 中的事件,从 Perfetto 中看就是对应 App 的 WaitQueue 减少。如果主线程发生卡顿,那么 Input 事件没有及时被消耗,也会在 WaitQueue 这里体现出来,如下图:
Input 刷新与 Vsync
Input 的刷新取决于触摸屏的采样,目前比较多的屏幕采样率是 120Hz,对应就是 8.3ms 采样一次,我们来看一下其在 Perfetto 上的展示。
可以看到上图中, InputReader 每隔 8.3ms 就可以读上来一个数据,交给 InputDispatcher 去分发给 App ,那么是不是屏幕采样率越高越好呢?也不一定,比如上面那张图,虽然 InputReader 每隔 8.3ms 就可以读上来一个数据给 InputDispatcher 去分发给 App ,但是从 WaitQueue 的表现来看,应用并没有消耗这个 Input 事件,这是为什么呢?
原因在于应用消耗 Input 事件的时机是 Vsync 信号来了之后,刷新率为 60Hz 的屏幕,一般系统也是 60 fps ,也就是说两个 Vsync 的间隔在 16.6ms ,这期间如果有两个或者三个 Input 事件,那么必然有一个或者两个要被抛弃掉,只拿最新的那个。也就是说:
在屏幕刷新率和系统 FPS 都是 60 的时候,盲目提高触摸屏的采样率,是没有太大的效果的,反而有可能出现上面图中那样,有的 Vsync 周期中有两个 Input 事件,而有的 Vsync 周期中有三个 Input 事件,这样造成事件不均匀,可能会使 UI 产生抖动。
在屏幕刷新率和系统 FPS 都是 60 的时候,使用 120Hz 采样率的触摸屏就可以了。
如果在屏幕刷新率和系统 FPS 都是 90 的时候 ,那么 120Hz 采样率的触摸屏显然不够用了,这时候应该采用 180Hz 采样率的屏幕。
Input 调试信息
Dumpsys Input 主要是 Debug 用,我们也可以来看一下其中的一些关键信息,到时候遇到了问题也可以从这里面找 , 其命令如下:
adb shell dumpsys input
其中的输出比较多,我们终点截取 Device 信息、InputReader、InputDispatcher 三段来看就可以了。Device 信息。
主要是目前连接上的 Device 信息,下面摘取的是 touch 相关的:
3: main_touch
Classes: 0x00000015
Path: /dev/input/event6
Enabled: true
Descriptor: 4055b8a032ccf50ef66dbe2ff99f3b2474e9eab5
Location: main_touch/input0
ControllerNumber: 0
UniqueId:
Identifier: bus=0x0000, vendor=0xbeef, product=0xdead, version=0x28bb
KeyLayoutFile: /system/usr/keylayout/main_touch.kl
KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
ConfigurationFile:
HaveKeyboardLayoutOverlay: false
Input Reader 状态
InputReader 这里就是当前 Input 事件的一些展示。
Device 3: main_touch
Generation: 24
IsExternal: false
HasMic: false
Sources: 0x00005103
KeyboardType: 1
Motion Ranges:
X: source=0x00005002, min=0.000, max=1079.000, flat=0.000, fuzz=0.000, resolution=0.000
Y: source=0x00005002, min=0.000, max=2231.000, flat=0.000, fuzz=0.000, resolution=0.000
PRESSURE: source=0x00005002, min=0.000, max=1.000, flat=0.000, fuzz=0.000, resolution=0.000
SIZE: source=0x00005002, min=0.000, max=1.000, flat=0.000, fuzz=0.000, resolution=0.000
TOUCH_MAJOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
TOUCH_MINOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
TOOL_MAJOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
TOOL_MINOR: source=0x00005002, min=0.000, max=2479.561, flat=0.000, fuzz=0.000, resolution=0.000
Keyboard Input Mapper:
Parameters:
HasAssociatedDisplay: false
OrientationAware: false
HandlesKeyRepeat: false
KeyboardType: 1
Orientation: 0
KeyDowns: 0 keys currently down
MetaState: 0x0
DownTime: 521271703875000
Touch Input Mapper (mode - direct):
Parameters:
GestureMode: multi-touch
DeviceType: touchScreen
AssociatedDisplay: hasAssociatedDisplay=true, isExternal=false, displayId=''
OrientationAware: true
Raw Touch Axes:
X: min=0, max=1080, flat=0, fuzz=0, resolution=0
Y: min=0, max=2232, flat=0, fuzz=0, resolution=0
Pressure: min=0, max=127, flat=0, fuzz=0, resolution=0
TouchMajor: min=0, max=512, flat=0, fuzz=0, resolution=0
TouchMinor: unknown range
ToolMajor: unknown range
ToolMinor: unknown range
Orientation: unknown range
Distance: unknown range
TiltX: unknown range
TiltY: unknown range
TrackingId: min=0, max=65535, flat=0, fuzz=0, resolution=0
Slot: min=0, max=20, flat=0, fuzz=0, resolution=0
Calibration:
touch.size.calibration: geometric
touch.pressure.calibration: physical
touch.orientation.calibration: none
touch.distance.calibration: none
touch.coverage.calibration: none
Affine Transformation:
X scale: 1.000
X ymix: 0.000
X offset: 0.000
Y xmix: 0.000
Y scale: 1.000
Y offset: 0.000
Viewport: displayId=0, orientation=0, logicalFrame=[0, 0, 1080, 2232], physicalFrame=[0, 0, 1080, 2232], deviceSize=[1080, 2232]
SurfaceWidth: 1080px
SurfaceHeight: 2232px
SurfaceLeft: 0
SurfaceTop: 0
PhysicalWidth: 1080px
PhysicalHeight: 2232px
PhysicalLeft: 0
PhysicalTop: 0
SurfaceOrientation: 0
Translation and Scaling Factors:
XTranslate: 0.000
YTranslate: 0.000
XScale: 0.999
YScale: 1.000
XPrecision: 1.001
YPrecision: 1.000
GeometricScale: 0.999
PressureScale: 0.008
SizeScale: 0.002
OrientationScale: 0.000
DistanceScale: 0.000
HaveTilt: false
TiltXCenter: 0.000
TiltXScale: 0.000
TiltYCenter: 0.000
TiltYScale: 0.000
Last Raw Button State: 0x00000000
Last Raw Touch: pointerCount=1
[0]: id=0, x=660, y=1338, pressure=44, touchMajor=44, touchMinor=44, toolMajor=0, toolMinor=0, orientation=0, tiltX=0, tiltY=0, distance=0, toolType=1, isHovering=false
Last Cooked Button State: 0x00000000
Last Cooked Touch: pointerCount=1
[0]: id=0, x=659.389, y=1337.401, pressure=0.346, touchMajor=43.970, touchMinor=43.970, toolMajor=43.970, toolMinor=43.970, orientation=0.000, tilt=0.000, distance=0.000, toolType=1, isHovering=false
Stylus Fusion:
ExternalStylusConnected: false
External Stylus ID: -1
External Stylus Data Timeout: 9223372036854775807
External Stylus State:
When: 9223372036854775807
Pressure: 0.000000
Button State: 0x00000000
Tool Type: 0
InputDispatcher 状态
InputDispatch 这里的重要信息主要包括:
FocusedApplication :当前获取焦点的应用。
FocusedWindow : 当前获取焦点的窗口。
TouchStatesByDisplay。
Windows :所有的 Window。
MonitoringChannels :Window 对应的 Channel。
Connections :所有的连接。
AppSwitch: not pending。
Configuration。
Input Dispatcher State:
DispatchEnabled: 1
DispatchFrozen: 0
FocusedApplication: name='AppWindowToken{ac6ec28 token=Token{a38a4b ActivityRecord{7230f1a u0 com.meizu.flyme.launcher/.Launcher t13}}}', dispatchingTimeout=5000.000ms
FocusedWindow: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}'
TouchStatesByDisplay:
0: down=true, split=true, deviceId=3, source=0x00005002
Windows:
0: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', pointerIds=0x80000000, targetFlags=0x105
1: name='Window{8cb8f7 u0 com.android.systemui.ImageWallpaper}', pointerIds=0x0, targetFlags=0x4102
Windows:
2: name='Window{ba2fc6b u0 NavigationBar}', displayId=0, paused=false, hasFocus=false, hasWallpaper=false, visible=true, canReceiveKeys=false, flags=0x21840068, type=0x000007e3, layer=0, frame=[0,2136][1080,2232], scale=1.000000, touchableRegion=[0,2136][1080,2232], inputFeatures=0x00000000, ownerPid=26514, ownerUid=10033, dispatchingTimeout=5000.000ms
3: name='Window{72b7776 u0 StatusBar}', displayId=0, paused=false, hasFocus=false, hasWallpaper=false, visible=true, canReceiveKeys=false, flags=0x81840048, type=0x000007d0, layer=0, frame=[0,0][1080,84], scale=1.000000, touchableRegion=[0,0][1080,84], inputFeatures=0x00000000, ownerPid=26514, ownerUid=10033, dispatchingTimeout=5000.000ms
9: name='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', displayId=0, paused=false, hasFocus=true, hasWallpaper=true, visible=true, canReceiveKeys=true, flags=0x81910120, type=0x00000001, layer=0, frame=[0,0][1080,2232], scale=1.000000, touchableRegion=[0,0][1080,2232], inputFeatures=0x00000000, ownerPid=27619, ownerUid=10021, dispatchingTimeout=5000.000ms
MonitoringChannels:
0: 'WindowManager (server)'
RecentQueue: length=10
MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (524.5, 1306.4)]), policyFlags=0x62000000, age=61.2ms
MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (543.5, 1309.4)]), policyFlags=0x62000000, age=54.7ms
PendingEvent: <none>
InboundQueue: <empty>
ReplacedKeys: <empty>
Connections:
0: channelName='WindowManager (server)', windowName='monitor', status=NORMAL, monitor=true, inputPublisherBlocked=false
OutboundQueue: <empty>
WaitQueue: <empty>
5: channelName='72b7776 StatusBar (server)', windowName='Window{72b7776 u0 StatusBar}', status=NORMAL, monitor=false, inputPublisherBlocked=false
OutboundQueue: <empty>
WaitQueue: <empty>
6: channelName='ba2fc6b NavigationBar (server)', windowName='Window{ba2fc6b u0 NavigationBar}', status=NORMAL, monitor=false, inputPublisherBlocked=false
OutboundQueue: <empty>
WaitQueue: <empty>
12: channelName='3c007ad com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher (server)', windowName='Window{3c007ad u0 com.meizu.flyme.launcher/com.meizu.flyme.launcher.Launcher}', status=NORMAL, monitor=false, inputPublisherBlocked=false
OutboundQueue: <empty>
WaitQueue: length=3
MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (634.4, 1329.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=17.4ms, wait=16.8ms
MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (647.4, 1333.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=11.1ms, wait=10.4ms
MotionEvent(deviceId=3, source=0x00005002, action=MOVE, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, edgeFlags=0x00000000, xPrecision=1.0, yPrecision=1.0, displayId=0, pointers=[0: (659.4, 1337.4)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=2, age=5.2ms, wait=4.6ms
AppSwitch: not pending
Configuration:
KeyRepeatDelay: 50.0ms
KeyRepeatTimeout: 500.0ms
Binder
很多卡顿问题和响应速度的问题,是因为跨进程 binder 通信的时候,锁竞争导致 binder 通信事件变长,影响了调用端。最常见的就是应用渲染线程 dequeueBuffer 的时候 SurfaceFlinger 主线程阻塞导致 dequeueBuffer 耗时,从而导致应用渲染出现卡顿; 或者 SystemServer 中的 AMS 或者 WMS 持锁方法等待太多, 导致应用调用的时候等待时间比较长导致主线程卡顿。
Binder 架构图
Binder调用图解
Binder 主要是用来跨进程进行通信,可以看下面这张图,简单显示了在 Perfetto 中 ,Binder 通信是如何显示的。
点击 Binder 可以查看其详细信息,其中有的信息在分析问题的时候可以用到,这里不做过多的描述。
Binder 持锁图解
Monitor 指的是当前锁对象的池,在 Java 中,每个对象都有两个池,锁(monitor)池和等待池:
锁池(同步队列 SynchronizedQueue ):假设线程 A 已经拥有了某个对象(注意:不是类 )的锁,而其它的线程想要调用这个对象的某个 synchronized 方法(或者 synchronized 块),由于这些线程在进入对象的 synchronized 方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程 A 拥有,所以这些线程就进入了该对象的锁池中。
这里用了争夺(contention)这个词,意思是这里由于在和目前对象的锁正被其他对象(Owner)所持有,所以没法得到该对象的锁的拥有权,所以进入该对象的锁池。
Owner : 指的是当前拥有这个对象的锁的对象。这里是 Binder:1605_B,4667 是其线程 ID。
HandlerThread
BackgroundThread
com/android/internal/os/BackgroundThread.java
private BackgroundThread() {
super("android.bg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
}
Systrace 中的 BackgroundThread
BackgroundThread 在系统中使用比较多,许多对性能没有要求的任务,一般都会放到 BackgroundThread 中去执行。
ServiceThread
ServiceThread 继承自 HandlerThread ,下面介绍的几个工作线程都是继承自 ServiceThread ,分别实现不同的功能,根据线程功能不同,其线程优先级也不同:UIThread、IoThread、DisplayThread、AnimationThread、FgThread、SurfaceAnimationThread。
每个 Thread 都有自己的 Looper 、Thread 和 MessageQueue,互相不会影响。Android 系统根据功能,会使用不同的 Thread 来完成。
UiThread
com/android/server/UiThread.java
private UiThread() {
super("android.ui", Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
}
Systrace 中的 UiThread,一般分析手写笔问题的时候会看。
UiThread 被使用的地方如下,具体的功能可以自己去源码里面查看,关键字是 UiThread.get()。
IoThread
com/android/server/IoThread.java
private IoThread() {
super("android.io", android.os.Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/);
}
IoThread 被使用的地方如下,具体的功能可以自己去源码里面查看,关键字是 IoThread.get()。
DisplayThread
com/android/server/DisplayThread.java
private DisplayThread() {
// DisplayThread runs important stuff, but these are not as important as things running in
// AnimationThread. Thus, set the priority to one lower.
super("android.display", Process.THREAD_PRIORITY_DISPLAY + 1, false /*allowIo*/);
}
Systrace 中的 DisplayThread
AnimationThread
com/android/server/AnimationThread.java
private AnimationThread() {
super("android.anim", THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
}
Systrace 中的 AnimationThread
AnimationThread 在源码中的使用,可以看到 WindowAnimator 的动画执行也是在。 AnimationThread 线程中的,Android P 增加了一个 SurfaceAnimationThread 来分担。 AnimationThread 的部分工作,来提高 WindowAnimation 的动画性能。
FgThread
com/android/server/FgThread.java
private FgThread() {
super("android.fg", android.os.Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/);
}
Systrace 中的 FgThread
FgThread 在源码中的使用,可以自己搜一下,下面是具体的使用的一个例子。
FgThread.getHandler().post(() -> {
synchronized (mLock) {
if (mStartedUsers.get(userIdToLockF) != null) {
Slog.w(TAG, "User was restarted, skipping key eviction");
return;
}
}
try {
mInjector.getStorageManager().lockUserKey(userIdToLockF);
} catch (RemoteException re) {
throw re.rethrowAsRuntimeException();
}
if (userIdToLockF == userId) {
for (final KeyEvictedCallback callback : keyEvictedCallbacks) {
callback.keyEvicted(userId);
}
}
});
SurfaceAnimationThread
com/android/server/wm/SurfaceAnimationThread.java
private SurfaceAnimationThread() {
super("android.anim.lf", THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
}
Systrace 中的 SurfaceAnimationThread。
SurfaceAnimationThread 的名字叫 android.anim.lf , 与 android.anim 有区别。
这个 Thread 主要是执行窗口动画,用于分担 android.anim 线程的一部分动画工作,减少由于锁导致的窗口动画卡顿问题。
SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider, AnimatorFactory animatorFactory, Transaction frameTransaction, PowerManagerInternal powerManagerInternal) {
SurfaceAnimationThread.getHandler().runWithScissors(() -> mChoreographer = getSfInstance(), 0 /* timeout */);
mFrameTransaction = frameTransaction;
mAnimationHandler = new AnimationHandler();
mAnimationHandler.setProvider(callbackProvider != null ? callbackProvider : new SfVsyncFrameCallbackProvider(mChoreographer));
mAnimatorFactory = animatorFactory != null ? animatorFactory: SfValueAnimator::new;
mPowerManagerInternal = powerManagerInternal;
}