并行启动
本文面向已经读过 SystemServer.run、SystemServiceManager、BootPhase阶段 和 服务启动耗时 的读者。此前文章解释了服务启动顺序和 trace;本文专门回答:哪些工作真的离开了 SystemServer 主线程,Future 由谁保存,消费者在哪里等待,以及一次“并行优化”为什么可能只是把阻塞移动到更晚的 phase。
Android 17 的通用 SystemServiceManager.startService() 没有并行构造所有服务。它仍同步调用构造器和 onStart();并行发生在服务主动提交的初始化任务,以及 parallelize_onbootphase 开启后的 onBootPhase() 回调。读完后,你应能从一个 submit() 追踪到最终状态发布,识别无 Future、单 Future、Future 链和阶段屏障的差异,并判断任务是否适合放进启动线程池。
1. 并行边界
SystemServer 主线程先准备 Looper,再创建初始化线程池。线程池启动后立即提交 SystemConfig 磁盘读取,使它与 android_servers native library 加载、system context 创建等工作重叠。
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
Looper.prepareMainLooper();
Looper.getMainLooper().setSlowLogThresholdMs(
SLOW_DISPATCH_THRESHOLD_MS,
SLOW_DELIVERY_THRESHOLD_MS);
SystemServiceRegistry.sEnableServiceNotFoundWtf = true;
SystemServerInitThreadPool.start();
mDumper.addDumpable(SystemServerInitThreadPool.getInstance());
startSystemConfigInit(t);
System.loadLibrary("android_servers");服务组本身仍在一个同步调用链中:
startBootstrapServices(t);
startCoreServices(t);
startOtherServices(t);
startApexServices(t);
updateWatchdogTimeout(t);
CriticalEventLog.getInstance().logSystemServerStarted();startService() 反射构造对象、加入 mServices,随后直接调用 service.onStart()。只有构造器或 onStart() 内部主动 submit() 的部分才会异步。
源码文件:frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
public void startService(@NonNull final SystemService service) {
String className = service.getClass().getName();
if (mServiceClassnames.contains(className)) {
Slog.i(TAG, "Not starting an already started service " + className);
return;
}
mServiceClassnames.add(className);
mServices.add(service);
long time = SystemClock.elapsedRealtime();
try {
service.onStart();
} catch (RuntimeException ex) {
throw new RuntimeException("Failed to start service "
+ service.getClass().getName()
+ ": onStart threw an exception", ex);
}
warnIfTooLong(SystemClock.elapsedRealtime() - time,
service, "onStart");
}因此“并行服务启动”更准确地说是并行初始化、同步发布。Binder/LocalService 何时可见,仍由各服务的 onStart() 或后续 BootPhase 决定,不能因为后台任务已经提交就认为服务已经 ready。
2. 线程池所有权
SystemServerInitThreadPool 是进程级 singleton。LOCK 保护实例生命周期,mPendingTasks 自己的 monitor 保护 pending 描述和 shutdown 状态;ExecutorService 拥有 worker 与任务队列。
源码文件:frameworks/base/services/core/java/com/android/server/SystemServerInitThreadPool.java
private static final int SHUTDOWN_TIMEOUT_MILLIS = 20000;
private static final Object LOCK = new Object();
@GuardedBy("LOCK")
private static SystemServerInitThreadPool sInstance;
private final int mSize;
private final ExecutorService mService;
@GuardedBy("mPendingTasks")
private final List<String> mPendingTasks = new ArrayList<>();
@GuardedBy("mPendingTasks")
private boolean mShutDown;
private SystemServerInitThreadPool() {
mSize = Runtime.getRuntime().availableProcessors();
Slog.i(TAG, "Creating instance with " + mSize + " threads");
mService = ConcurrentUtils.newFixedThreadPool(mSize,
"system-server-init-thread",
Process.THREAD_PRIORITY_FOREGROUND);
}线程数等于当前 runtime 报告的可用处理器数,没有固定上限。固定线程池按需创建名为 system-server-init-thread1、thread2 等 worker,并在 worker 入口设置 foreground Linux priority。
源码文件:frameworks/base/core/java/com/android/internal/util/ConcurrentUtils.java
public static ExecutorService newFixedThreadPool(
int nThreads, String poolName,
int linuxThreadPriority) {
return Executors.newFixedThreadPool(nThreads,
new ThreadFactory() {
private final AtomicInteger threadNum =
new AtomicInteger(0);
@Override
public Thread newThread(final Runnable r) {
return new Thread(poolName
+ threadNum.incrementAndGet()) {
@Override
public void run() {
Process.setThreadPriority(
linuxThreadPriority);
r.run();
}
};
}
});
}这意味着 CPU 密集任务会与 SystemServer 主线程、Binder 线程和其他前台线程争夺核心;磁盘任务则可能相互放大 I/O wait。线程数多只能增加可并发度,不保证关键路径缩短。
实例状态有清晰边界:
static void start() {
synchronized (LOCK) {
Preconditions.checkState(sInstance == null,
TAG + " already started");
sInstance = new SystemServerInitThreadPool();
}
}
static SystemServerInitThreadPool getInstance() {
SystemServerInitThreadPool instance;
synchronized (LOCK) {
Preconditions.checkState(sInstance != null,
"Cannot get " + TAG
+ " - it has been shut down");
instance = sInstance;
}
return instance;
}启动两次会失败;成功 shutdown 后 sInstance=null,后续 submit 在 getInstance() 处失败。它不是可长期复用的 system_server 后台池,而是只服务启动期的资源。
3. 任务状态
submit() 返回 Future,但线程池本身只保存 description。description 从提交前一直保留到任务正常完成;它同时代表排队中和运行中的任务,不是精确状态机。
源码文件:frameworks/base/services/core/java/com/android/server/SystemServerInitThreadPool.java
private <T> @NonNull Future<T> submitTask(
@NonNull Callable<T> callable,
@NonNull 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);
if (IS_DEBUGGABLE) {
Slog.d(TAG,
"Started executing " + description);
}
T result = null;
try {
result = callable.call();
} catch (RuntimeException e) {
Slog.e(TAG,
"Failure in " + description + ": " + e,
e);
traceLog.traceEnd();
throw e;
}
synchronized (mPendingTasks) {
mPendingTasks.remove(description);
}
if (IS_DEBUGGABLE) {
Slog.d(TAG,
"Finished executing " + description);
}
traceLog.traceEnd();
return result;
});
}正常路径的状态变化是:
- submit 方在
mPendingTasks下登记 description; - ExecutorService 排队,Future 归调用方所有;
- worker 创建独立
SystemServerTimingAsynctrace; - callable 写入它自己的结果对象;
- 成功后移除 description,Future 变为 completed。
RuntimeException 会记录错误、结束 trace 并让 Future exceptional,但不会执行后面的 mPendingTasks.remove();description 会保留下来用于诊断。Callable 还允许抛 checked exception,Error 也不属于 RuntimeException,这两类路径不会经过这里的显式日志和 traceEnd()。因此调用者不能把 pending list 当成“当前仍执行”的绝对真相,Future 才是任务结果的 owner。
4. 依赖屏障
异步任务是否正确,不取决于 submit 位置,而取决于最终消费者能否看到完整状态。当前源码使用三种不同屏障。
4.1 隐式屏障
SystemConfig 的 Future 被直接丢弃。真正的同步由 singleton class monitor 提供:worker 在 synchronized 块中构造实例;任何较晚调用 getInstance() 的线程都会在同一 monitor 上等待。
源码文件:
frameworks/base/services/java/com/android/server/SystemServer.javaframeworks/base/services/core/java/com/android/server/SystemConfig.java
private void startSystemConfigInit(
TimingsTraceAndSlog t) {
Slog.i(TAG, "Reading configuration...");
final String tagSystemConfig =
"ReadingSystemConfig";
t.traceBegin(tagSystemConfig);
SystemServerInitThreadPool.submit(
SystemConfig::getInstance,
tagSystemConfig);
t.traceEnd();
}提交方不保存返回的 Future;下面的 singleton 实现本身承担等待和结果发布:
public static SystemConfig getInstance() {
if (!isSystemProcess()) {
Slog.wtf(TAG,
"SystemConfig is being accessed by a process other than system_server.");
}
synchronized (SystemConfig.class) {
if (sInstance == null) {
sInstance = new SystemConfig();
}
return sInstance;
}
}这种模式适合幂等 singleton 初始化:提前任务成功时,消费者只读现成实例;任务尚未完成时,消费者阻塞在 class monitor;worker 构造失败时 Future 虽无人读取,但 sInstance 仍为 null,后续消费者可能在自己的线程重新构造。代价是等待在 trace 中表现为锁竞争,而不是清晰的 Future.get() section。
4.2 Phase发布
SensorService 把 native service 启动放进构造器提交的 Future,onStart() 只发布 LocalService;到 PHASE_WAIT_FOR_SENSOR_SERVICE 才等待 Future,确保后续 phase 可以安全使用 native pointer。
源码文件:frameworks/base/services/core/java/com/android/server/sensors/SensorService.java
public SensorService(Context ctx) {
super(ctx);
synchronized (mLock) {
mSensorServiceStart =
SystemServerInitThreadPool.submit(() -> {
TimingsTraceAndSlog traceLog =
TimingsTraceAndSlog.newAsyncLog();
traceLog.traceBegin(
START_NATIVE_SENSOR_SERVICE);
long ptr = startSensorServiceNative(
new ProximityListenerDelegate());
synchronized (mLock) {
mPtr = ptr;
}
traceLog.traceEnd();
}, START_NATIVE_SENSOR_SERVICE);
}
}
@Override
public void onStart() {
LocalServices.addService(
SensorManagerInternal.class,
new LocalService());
}消费者屏障位于 phase 200:
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_WAIT_FOR_SENSOR_SERVICE) {
ConcurrentUtils.waitForFutureNoInterrupt(
mSensorServiceStart,
START_NATIVE_SENSOR_SERVICE);
synchronized (mLock) {
mSensorServiceStart = null;
}
}
}Future completion建立 happens-before 关系,随后 mSensorServiceStart=null 表示启动任务不再是状态 owner。mPtr 仍由 mLock 保护。这里的关键不变量是:任何依赖 native sensor ready 的后续阶段都必须排在 phase 200 之后。
ContextHub 使用更保守的版本:onStart() 什么也不发布,phase 500 等初始化完成后才注册 Binder 服务。
源码文件:frameworks/base/services/core/java/com/android/server/ContextHubSystemService.java
public ContextHubSystemService(Context context) {
super(context);
mInit = SystemServerInitThreadPool.submit(() -> {
mContextHubService = new ContextHubService(
context,
IContextHubWrapper.getContextHubWrapper());
}, "Init ContextHubSystemService");
}
@Override
public void onStart() {
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
ConcurrentUtils.waitForFutureNoInterrupt(
mInit,
"Wait for ContextHubSystemService init");
mInit = null;
publishBinderService(
Context.CONTEXTHUB_SERVICE,
mContextHubService);
}
}两者的区别是发布时机:Sensor LocalService 提前可见但能力受 phase 契约约束,ContextHub Binder 在状态 ready 后才可见。选择哪种模式应由早期消费者需求决定。
4.3 Future链
Secondary Zygote preload、WebView preparation 和第三方应用启动构成一条两级 Future 链。第一个任务在 startOtherServices() 较早位置提交,第二个 worker 等待它,SystemServer 主线程最终等待第二个 Future 后才推进 phase 600。
源码文件:frameworks/base/services/java/com/android/server/SystemServer.java
final String SECONDARY_ZYGOTE_PRELOAD =
"SecondaryZygotePreload";
mZygotePreload = SystemServerInitThreadPool.submit(() -> {
try {
Slog.i(TAG, SECONDARY_ZYGOTE_PRELOAD);
TimingsTraceAndSlog traceLog =
TimingsTraceAndSlog.newAsyncLog();
traceLog.traceBegin(SECONDARY_ZYGOTE_PRELOAD);
String[] abis32 = Build.SUPPORTED_32_BIT_ABIS;
if (abis32.length > 0 &&
!Process.ZYGOTE_PROCESS.preloadDefault(
abis32[0])) {
Slog.e(TAG,
"Unable to preload default resources for secondary");
}
traceLog.traceEnd();
} catch (Exception ex) {
Slog.e(TAG,
"Exception preloading default resources",
ex);
}
}, SECONDARY_ZYGOTE_PRELOAD);WebView worker 取得第一个 Future:
final String WEBVIEW_PREPARATION =
"WebViewFactoryPreparation";
Future<?> webviewPrep = null;
if (mWebViewUpdateService != null) {
webviewPrep = SystemServerInitThreadPool.submit(() -> {
Slog.i(TAG, WEBVIEW_PREPARATION);
TimingsTraceAndSlog traceLog =
TimingsTraceAndSlog.newAsyncLog();
traceLog.traceBegin(WEBVIEW_PREPARATION);
ConcurrentUtils.waitForFutureNoInterrupt(
mZygotePreload,
"Zygote preload");
mZygotePreload = null;
mWebViewUpdateService
.prepareWebViewInSystemServer();
traceLog.traceEnd();
}, WEBVIEW_PREPARATION);
}主线程在允许第三方代码运行前收口:
t.traceBegin("PhaseThirdPartyAppsCanStart");
if (webviewPrep != null) {
ConcurrentUtils.waitForFutureNoInterrupt(
webviewPrep,
WEBVIEW_PREPARATION);
}
mSystemServiceManager.startBootPhase(t,
SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
t.traceEnd();Secondary preload 内部捕获 Exception 并只写日志,所以第一个 Future 会正常完成;WebView worker 不会因为 secondary preload 失败而自动失败。这是明确的降级策略。相反,prepareWebViewInSystemServer() 抛出的 RuntimeException 会让 webviewPrep exceptional,主线程等待时终止 phase 600 推进。
waitForFutureNoInterrupt() 没有 timeout。名称也不表示忽略中断:它重新设置 interrupt flag,然后抛出 IllegalStateException;ExecutionException 则包装成 RuntimeException。
源码文件:frameworks/base/core/java/com/android/internal/util/ConcurrentUtils.java
public static <T> T waitForFutureNoInterrupt(
Future<T> future, String description) {
try {
return future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException(
description + " interrupted");
} catch (ExecutionException e) {
throw new RuntimeException(
description + " failed", e);
}
}Future 链若把 consumer 和 producer 都塞进同一个已饱和固定线程池,可能发生线程饥饿:所有 worker 都在等待尚未获得 worker 的 producer。当前 secondary preload 比 WebView 更早提交,且主线程继续做其他工作,但新增链式任务仍必须检查提交顺序和池容量。
5. BootPhase并行
Android 17 加入 parallelize_onbootphase aconfig flag。声明没有显式 value override;Aconfig 的默认 state 是 DISABLED,因此当前 tag 的默认路径仍串行。设备或产品 flag 配置可以启用它,分析运行行为时必须读取实际构建配置,不能只看源码分支。
源码文件:
frameworks/base/services/core/java/com/android/server/flags/services.aconfigbuild/make/tools/aconfig/aconfig/src/commands.rs
flag {
name: "parallelize_onbootphase"
namespace: "system_performance"
description: "Run services' onBootPhase methods in parallel"
bug: "459252932"
}Aconfig 在没有 values 输入覆盖时,为声明创建默认的 disabled、read-write 状态:
pub const DEFAULT_FLAG_STATE: ProtoFlagState =
ProtoFlagState::DISABLED;
pub const DEFAULT_FLAG_PERMISSION: ProtoFlagPermission =
ProtoFlagPermission::READ_WRITE;flag 开启后,SystemServiceManager 先把声明 serial 的服务分组并在主线程依次执行,再一次性提交其余服务,最后等待所有 Future。
源码文件:frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
final int serviceLen = mServices.size();
ArrayList<SystemService> serialServices =
new ArrayList<>();
ArrayList<SystemService> parallelServices =
new ArrayList<>();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
if (!Flags.parallelizeOnbootphase()
|| service.getBootPhaseSerial(mCurrentPhase)) {
serialServices.add(service);
} else {
parallelServices.add(service);
}
}串行组完成后才开始并行组,所以 setBootPhaseSerial() 同时表达两个约束:回调必须在主线程执行,并且必须先于本 phase 的并行回调完成。
if (Flags.parallelizeOnbootphase()) {
final Future[] futures =
new Future[parallelServices.size()];
for (int i = 0; i < parallelServices.size(); i++) {
final SystemService service =
parallelServices.get(i);
futures[i] = SystemServerInitThreadPool.submit(() -> {
long time = SystemClock.elapsedRealtime();
try {
service.onBootPhase(mCurrentPhase);
} catch (Exception ex) {
throw new RuntimeException(
"Failed to boot service "
+ service.getClass().getName()
+ ": onBootPhase threw an exception during phase "
+ mCurrentPhase, ex);
}
warnIfTooLong(
SystemClock.elapsedRealtime() - time,
service, "onBootPhase");
}, "OnBootPhase_" + phase + "_"
+ service.getClass().getName());
}
for (int i = 0; i < futures.length; i++) {
try {
futures[i].get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}Future 按数组顺序 get() 不会让任务按顺序执行,因为所有任务已经提交;它只决定主线程先观察哪个结果。phase 是 barrier:方法返回前所有被等待的回调必须完成。若某个 Future 先抛异常,等待循环立即退出,其他已经提交的回调不会自动取消,可能在启动异常传播期间继续运行。
SystemService 类注释仍写着生命周期方法在 system_server main looper 调用;flag 分支是一个条件性的新契约。NotificationManagerService 的 phase 500 初始化包含 main-thread-only 的 RoleObserver,因此构造器在 flag 开启时把该 phase 标为 serial。
源码文件:frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
public NotificationManagerService(Context context) {
super(context);
if (com.android.server.flags.Flags
.parallelizeOnbootphase()) {
setBootPhaseSerial(
SystemService.PHASE_SYSTEM_SERVICES_READY);
}
Notification.processAllowlistToken =
ALLOWLIST_TOKEN;
}该 serial 声明保护的是 callback 内部的 main Looper 不变量;对应实现会直接检查当前线程:
private void updateTrampolineExemptUidsForUsers(
UserHandle... users) {
Preconditions.checkState(
mMainLooper.isCurrentThread());
ArraySet<Integer> oldUids =
mTrampolineExemptUids;
ArraySet<Integer> newUids =
new ArraySet<>();一个服务若在 onBootPhase 中访问无锁共享字段、依赖同 phase 中另一个服务先完成、创建只能属于主 Looper 的对象,必须声明对应 phase serial,或重构为显式依赖屏障。仅仅“测试没崩”不足以说明它线程安全。
6. 失败与关闭
PHASE_BOOT_COMPLETED 的所有回调结束后,SystemServiceManager 记录 TotalBootTime 并关闭线程池。
源码文件:frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
final long totalBootTime =
SystemClock.uptimeMillis()
- mRuntimeStartUptime;
t.logDuration("TotalBootTime", totalBootTime);
shutdownInitThreadPool();
}关闭先在 pending lock 下设置 mShutDown=true,再停止接收任务并最多等待 20 秒。
源码文件:frameworks/base/services/core/java/com/android/server/SystemServerInitThreadPool.java
synchronized (sInstance.mPendingTasks) {
sInstance.mShutDown = true;
}
sInstance.mService.shutdown();
final boolean terminated;
try {
terminated = sInstance.mService.awaitTermination(
SHUTDOWN_TIMEOUT_MILLIS,
TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
dumpStackTraces();
t.traceEnd();
throw new IllegalStateException(
TAG + " init interrupted");
}超时会先抓 system_server 和 Watchdog 关注的 native PID 栈,再调用 shutdownNow(),最后把未启动 Runnable 与 pending description 一起放进异常。
if (!terminated) {
dumpStackTraces();
}
final List<Runnable> unstartedRunnables =
sInstance.mService.shutdownNow();
if (!terminated) {
final List<String> copy = new ArrayList<>();
synchronized (sInstance.mPendingTasks) {
copy.addAll(sInstance.mPendingTasks);
}
t.traceEnd();
throw new IllegalStateException(
"Cannot shutdown. Unstarted tasks "
+ unstartedRunnables
+ " Unfinished tasks " + copy);
}
sInstance = null;这个 20 秒 timeout 只保护最终 shutdown。若主线程在 phase 500/600 的 Future.get() 永久等待,系统根本到不了 phase 1000,shutdown timeout 不会救场。需要超时语义的依赖必须在任务自身或专用 latch/API 中实现,不能依赖池关闭。
还要区分两种失败策略:
- 关键任务保存 Future 并在依赖点等待,exception 会传播到启动主线;
- fire-and-forget任务若失败,可能只有 worker 日志。Executor 已终止时 shutdown 仍可成功,即使 description 因异常留在 pending list。
因此每个 submit 都应明确消费者,而不是把“最终 shutdown 会等”当成错误传播机制。
7. 并行判定
适合并行的启动工作通常同时满足:输入已经冻结;只写自己拥有的状态;不需要主 Looper;没有持锁等待 Binder/HAL;能给出清晰的 ready 屏障;失败策略明确。
| 问题 | 可以并行的信号 | 应保留串行的信号 |
|---|---|---|
| 输入 | immutable config、独立文件集合 | 依赖另一个正在变化的 service field |
| 输出 | worker 私有对象,完成后一次发布 | 边计算边暴露半初始化对象 |
| 线程 | native/disk 工作不要求 Looper | Handler、Role/Window/UI 的主线程约束 |
| 锁 | worker 计算阶段不持全局锁 | Future A 持锁等待 Future B |
| 消费 | 明确 phase/Future/monitor barrier | “应该在用到前自然完成” |
| 失败 | abort 或已设计的功能降级 | 只记录日志但消费者要求强一致 |
SystemConfig 适合提前加载,因为 singleton monitor 可以保护一次性发布;ContextHub 适合到 phase 500 再发布 Binder;SensorService 明确用 phase 200 封口。相反,把两个互相调用的 onStart() 直接放进线程池会破坏服务注册顺序,并把确定性的依赖错误变成时序竞争。
优化时还应检查资源竞争。一个 worker section 自身变短,不代表总启动变快:foreground worker 可能让主线程更晚获得 CPU,多个 XML/dex 扫描可能争用存储,多个 Binder 等待可能占满所有 worker。
8. 验证方法
源码测试:frameworks/base/services/tests/servicestests/src/com/android/server/SystemServiceManagerTest.java 的 testSealStartedServices() 创建一个在 onStart() 中设置 AtomicBoolean 的服务,调用 startService() 后立刻断言状态为 true。它证明通用启动 API 在返回前已经执行完 onStart();没有证明服务内部提交的 Future 已完成。
源码文件:frameworks/base/services/tests/servicestests/src/com/android/server/SystemServiceManagerTest.java
AtomicBoolean serviceStarted =
new AtomicBoolean(false);
SystemService service1 =
new SystemService(getContext()) {
@Override
public void onStart() {
serviceStarted.set(true);
}
};
mSystemServiceManager.startService(service1);
assertTrue(serviceStarted.get());NotificationManagerService 的测试初始化会让 mocked main Looper 第一次 isCurrentThread() 返回 true,再直接执行 phase 500。输入是需要 RoleObserver 初始化的服务状态,关键条件是 main-thread check 能通过;它解释了为什么真实构造器要把该 phase 标为 serial,但测试没有覆盖 SystemServiceManager 的并行分组和 Future barrier。
源码文件:frameworks/base/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
when(mMainLooper.isCurrentThread())
.thenReturn(true)
.thenReturn(false);
if (upToBootPhase >=
SystemService.PHASE_SYSTEM_SERVICES_READY) {
mService.onBootPhase(
SystemService.PHASE_SYSTEM_SERVICES_READY,
mMainLooper);
}当前 tag 的 SystemServiceManagerTest 没有覆盖 parallelize_onbootphase、serial/parallel 顺序、worker 异常或 shutdown timeout;SystemServerInitThreadPool 也没有独立单元测试。修改这些机制时,至少应增加可控 Executor 与 latch 测试,而不能只依赖完整开机是否成功。
运行时可以从 owner、worker 和 barrier 三处取信息:
# 输入:当前system_server dumper。输出:线程数、Executor状态、shutdown标志和pending描述。
adb shell dumpsys system_server_dumper \
--name SystemServerInitThreadPool
# 输入:system_server启动日志。输出:任务开始/结束、worker异常和shutdown结果。
adb logcat -b system -v threadtime -d \
'SystemServerInitThreadPool:*' \
'SystemServerTimingAsync:*' '*:S'
# 输入:源码树。输出:全部submit点以及显式Future等待点,便于建立依赖图。
rg -n 'SystemServerInitThreadPool.submit|waitForFutureNoInterrupt|\.join\(\)' \
frameworks/base/services \
frameworks/base/core/java/com/android/internal/util/ConcurrentUtils.javaPerfetto 中应同时查看 InitThreadPoolExec:<description>、主线程 phase section、sched runnable time、Binder/I/O wait。若 worker 很忙但主线程在 Future 上等到相同终点,并行没有产生 overlap;若主线程 section 变短而 boot animation 或 phase 600 变慢,工作只是被移动。
检查自己是否读通这条源码,可以回答两个问题:SensorService 为什么能在 onStart() 发布 LocalService,却仍必须在 phase 200 等待;以及某个 BootPhase worker 抛异常后,为什么其他 worker 可能继续运行、主线程却已经开始传播启动失败。能把 owner、Future 和 phase barrier 对齐后,才有条件安全地增加新的并行任务。
