1. WMS 启动流程概述

做启动耗时优化,你得先把 WMS 的启动过程摸透。我之前带团队排查一个冷启动闪白屏的问题,折腾了两天,最后发现是 WMS 里某个阶段等了一把锁,白白多花了 800 毫秒。从那时起,我就养成了一个习惯——先画流程,再动手。

这一章我们先把基础打牢。WMS 启动到底分几个阶段?涉及哪些关键组件?耗时怎么定义、怎么算?这些问题搞清楚了,后面的监控才有方向。

1.1 启动阶段划分

说白了,WMS 不是一下子起来的,它是一层层推上去的。我个人习惯把它分成 3 个大阶段、6 个小阶段:

  1. 预初始化阶段:内核通过 init 进程拉起 SurfaceFlinger、创建 Display 设备。这时 WMS 还没出生。
  2. 核心初始化阶段:SystemServer 启动后,在 ServerThread 中创建 WindowManagerService 实例、初始化 WindowPolicy、DisplayManager、InputManager 等关键组件。
  3. 同步与就绪阶段:WMS 等待 SurfaceFlinger 的帧反馈、等待各 Display 准备完成,最后发出 `BOOT_COMPLETED` 广播。

嗯,这里要注意:你真正关心的“WMS 启动耗时”,一般不包含预初始化阶段。因为它跟 WMS 自身代码关系不大,更多是底层驱动的锅。

核心观点:WMS 启动耗时的监控起点,我建议从 SystemServer 调用 WindowManagerService.main() 开始,到 systemReady() 回调完成为止。

1.2 关键组件一览

WMS 启动过程中,至少有这几个“老人”需要你记住。我列个表格,方便你在分析 trace 时对照:

组件名称 职责简介 启动中耗时敏感点
WindowManagerService 核心,管理窗口层级、尺寸、动画 构造耗时、初始化策略、注册监听
DisplayManagerService 管理物理/虚拟屏幕 热插拔检测、Display 对象创建
InputManagerService 输入事件分发 读取配置、监听通道建立
WindowPolicy 系统窗口策略(状态栏、导航栏等) 读取配置、创建系统窗口布局
SurfaceFlinger 图形合成,WMS 的“下游”依赖 帧反馈等待、VSync 初始化

你有没有发现,真正的耗时大头往往不在 WMS 本身,而在它依赖的那些服务。我记得在一个项目中,InputManagerService 读取一根奇怪的 USB 键盘配置,硬生生多了 300ms——这就是典型的外设兼容性陷阱。

1.3 启动耗时的定义与计算方式

先统一口径:我们说的“WMS 启动耗时”,是指从 WMS 开始初始化 到 WMS 报告自己就绪 的时间差。怎么算?

方案一(最常用):抓 Slog 中的时间戳。

// 启动开始标记(在 WindowManagerService.main 中添加)
Log.d("WMS_Start", "init begin: " + SystemClock.elapsedRealtime());

// 启动结束标记(在 systemReady 回调中添加)
Log.d("WMS_Start", "init end: " + SystemClock.elapsedRealtime());

然后你用 `adb logcat -s WMS_Start` 拉出来,两个时间戳相减就行。曾经我就靠这个简单的打点,定位到某个定制 ROM 里 WMS 启动慢了 1.2 秒——后来发现是策略里加了一个大文件解码。

方案二(更精细):用 Perfetto / Systrace 圈出区间。

你可以在系统 trace 配置里开启 wm.trace_tag,大概长这样:

Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WMS.init");
// ... 初始化逻辑 ...
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);

然后打开生成的 trace 文件,直接看区间时长。这个方法比打 log 更准,因为 trace 能精确到微秒,而且能叠加 CPU 调度信息。

避坑提示:我曾经遇到一个情况——log 里显示 WMS 启动用了 1.5 秒,但用户体感只有 1 秒。后来才发现,WMS 的 systemReady 回调并不等于用户真正能看到界面。真正的“可用时间”要加上第一个 Activity 窗口绘制完成的时间。所以,建议你把“WMS 启动耗时”和“首帧显示耗时”分开统计。

另外,计算方式还有一点要注意:要不要排除休眠等待的时间?

我个人习惯算实际经过时间(wall time),因为用户感受到的就是从按下电源到看到桌面的总时长。但如果你在做性能回归,最好同时记录 CPU 活跃时长,两者对比能帮你判断是 CPU 资源不足还是代码逻辑慢。

1.4 知识结构总览

为了让你对整个启动流程有直观的认识,我画了一张图。它把阶段、组件、以及它们之间的依赖关系串在了一起。后续每一章我们都会围绕这张图展开。

WMS 启动流程与关键组件关系图 1. 预初始化 init → SurfaceFlinger → Display 2. 核心初始化 SystemServer → WMS 创建 3. 同步与就绪 等待帧反馈 → BOOT_COMPLETED 关键组件 WindowManagerService DisplayManager InputManager WindowPolicy SurfaceFlinger 说明:虚线表示 WMS 对 SurfaceFlinger 的异步依赖,不影响 WMS 本身的初始化完成 但影响首帧显示时间(通常与 WMS 启动耗时分开统计)

图里的三个阶段是串行的,但每个阶段内部的子步骤可能并行。比如在核心初始化阶段,DisplayManager 和 InputManager 是先后创建的,但它们的初始化可以跑在独立线程上(嗯,这块后面章节会细讲)。

小经验:我在做批量设备启动测试时,发现同一款机型的 WMS 启动耗时波动挺大,有时差 200ms。后来把 预初始化阶段的 Display 数量变化 也打日志,才锁定问题——某些外接显示器晚到,导致 WMS 等了太久。所以,监控不只看 WMS 本身,要连上下游一起看。