服务启动耗时
SystemServer 启动慢不能只看一条 SystemServerTiming 日志。Android 17 同时提供四层时间信息:TimingsTraceAndSlog 记录主线程嵌套阶段;SystemServiceManager.warnIfTooLong() 测量单个生命周期回调;SystemServerInitThreadPool 为并行任务建立异步 trace 和 pending task;BootCompleted 阶段记录总启动时间并关闭线程池。只有把四层对齐,才能区分“主线程自身慢”“等待 Future”“单服务 onStart 慢”和“并行任务占用资源拖慢关键路径”。
1. 主线程trace层次
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
try {
t.traceBegin("InitBeforeStartServices");
...
} finally {
t.traceEnd();
}
try {
t.traceBegin("StartServices");
startBootstrapServices(t);
startCoreServices(t);
startOtherServices(t);
startApexServices(t);
} finally {
t.traceEnd();
}顶层 section 把服务前准备和服务启动分开;各组内部继续嵌套 StartInstaller、StartPackageManagerService、StartWindowManagerService 等。Perfetto 中父 section 耗时包含子 section,不能把父子耗时相加,否则会重复计算。
2. 服务启动计时
源码文件:frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
public <T extends SystemService> T startService(
Class<T> serviceClass) {
try {
String name = serviceClass.getName();
Trace.traceBegin(
Trace.TRACE_TAG_SYSTEM_SERVER,
"StartService " + name);
T service = serviceClass
.getConstructor(Context.class)
.newInstance(mContext);
startService(service);
return service;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
public void startService(SystemService service) {
long time = SystemClock.elapsedRealtime();
service.onStart();
warnIfTooLong(
SystemClock.elapsedRealtime() - time,
service, "onStart");
}StartService <class> 覆盖反射构造和 onStart();warnIfTooLong 只测 onStart() 部分。因此 trace 长而 warning 不出现时,应检查构造器、静态初始化或 classloader;warning 出现则直接回到 onStart() 的发布、HAL 等待或磁盘 I/O。
3. 慢调用告警
private void warnIfTooLong(long duration,
SystemService service, String operation) {
if (duration > SERVICE_CALL_WARN_TIME_MS) {
Slog.w(TAG,
"Service " + service.getClass().getName()
+ " took " + duration + " ms in "
+ operation);
}
}告警是阈值检测,不会中止启动,也不证明服务发生死锁。duration 使用 elapsed realtime,不含设备 suspend 的时钟语义影响较少。相同 helper 也用于 onBootPhase 和用户生命周期,必须读 operation 字段才能知道慢在哪个 callback。
4. BootPhase耗时
long time = SystemClock.elapsedRealtime();
t.traceBegin("OnBootPhase_" + phase + "_"
+ service.getClass().getName());
service.onBootPhase(mCurrentPhase);
warnIfTooLong(
SystemClock.elapsedRealtime() - time,
service, "onBootPhase");
t.traceEnd();串行 callback 直接形成主线程嵌套 section。并行 callback 则提交线程池,Future 全部完成后 phase 才结束;一个慢并行服务会表现为 phase section 等待,但主线程 CPU 可能很低。
5. 初始化线程池
源码文件:frameworks/base/services/core/java/com/android/server/SystemServerInitThreadPool.java
private <T> Future<T> submitTask(
Callable<T> callable, String description) {
synchronized (mPendingTasks) {
Preconditions.checkState(!mShutDown,
TAG + " already shut down");
mPendingTasks.add(description);
}
return mService.submit(() -> {
TimingsTraceAndSlog traceLog =
TimingsTraceAndSlog.newAsyncLog();
traceLog.traceBegin(
"InitThreadPoolExec:" + description);
try {
return callable.call();
} finally {
synchronized (mPendingTasks) {
mPendingTasks.remove(description);
}
traceLog.traceEnd();
}
});
}任务提交时加入 pending list,执行线程创建独立 async trace;完成后移除。调用方拿到 Future,必须在依赖点等待。Perfetto 中应使用 description 把 submit、InitThreadPoolExec 和 Future.get/waitForFutureNoInterrupt 对齐。
6. 长任务与Watchdog
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
Watchdog.getInstance().pauseWatchingCurrentThread(
"packagemanagermain");
try {
mPackageManagerService = PackageManagerService.main(...);
} finally {
Watchdog.getInstance().resumeWatchingCurrentThread(
"packagemanagermain");
}PMS 是已知长任务,暂停的是当前线程 checker,不是关闭 Watchdog。耗时分析仍应使用 trace;pause 只防止合法长初始化触发误杀。若 pause 后没有 resume,Watchdog 保护会被削弱,因此 finally 是正确性边界。
7. 总启动时间
源码文件:frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
long totalBootTime = SystemClock.uptimeMillis()
- mRuntimeStartUptime;
t.logDuration("TotalBootTime", totalBootTime);
shutdownInitThreadPool();
}总耗时从 SystemServer 构造时记录的 runtime start uptime 到 BootCompleted phase;它不是从开机上电计算,也不含之后用户交互任务。线程池在该阶段关闭,之后再 submit 会抛 IllegalStateException,避免“启动线程池”演变成长期后台执行器。
8. 可执行采集方法
# 源码定位:阶段 trace、服务 trace 和线程池任务描述。
rg -n "TimingsTraceAndSlog|traceBegin\(|StartService|warnIfTooLong|InitThreadPoolExec|TotalBootTime" \
frameworks/base/services/java/com/android/server/SystemServer.java \
frameworks/base/services/core/java/com/android/server/SystemServiceManager.java \
frameworks/base/services/core/java/com/android/server/SystemServerInitThreadPool.java
# 运行时日志:输入 system_server 日志,输出慢生命周期告警和启动阶段。
adb logcat -b system -v threadtime \
'SystemServerTiming:*' 'SystemServiceManager:*' 'SystemServerInitThreadPool:*' '*:S'
# Perfetto:采集 sched、freq、binder、dalvik 与 system_server atrace,
# 再按 StartService/InitThreadPoolExec/OnBootPhase 名称关联。
adb shell perfetto -o /data/misc/perfetto-traces/system-server-start.pftrace \
-t 30s sched freq binder_driver dalvik am wm命令是否可用取决于设备构建、权限和 Perfetto 配置;没有 trace 权限时至少保留 SystemServerTiming、SystemServiceManager warning 和线程池 pending dump。
9. 反优化边界
- 把任务移到线程池可能缩短主线程 section,却增加锁竞争、GC 或后续 Future 等待;
- 缩短
onStart()不应通过延后发布必须立即可用的 Binder/LocalServices; - 提高 Watchdog timeout 不会优化启动,只会推迟故障发现;
- 删除预加载可能让 SystemServer 更快,却把成本复制到每个应用进程;
- 只比较 TotalBootTime 会掩盖某个阶段回归和并行资源竞争。
下一篇将分析 Trampoline/Lifecycle 包装模式如何影响启动 trace、实例 owner 和延迟初始化;不会重复本篇的耗时采集方法。
