Skip to content

并行启动

追踪 SystemServer 初始化任务的提交、Future 屏障、BootPhase 并行、异常传播和线程池关闭边界。

基于android-17.0.0_r1
AndroidSystemServerFutureBootPhase并发源码阅读

并行启动 ​

本文面向已经读过 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

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");

服务组本身仍在一个同步调用链中:

java
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

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

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

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。线程数多只能增加可并发度,不保证关键路径缩短。

实例状态有清晰边界:

java
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

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;
    });
}

正常路径的状态变化是:

  1. submit 方在 mPendingTasks 下登记 description;
  2. ExecutorService 排队,Future 归调用方所有;
  3. worker 创建独立 SystemServerTimingAsync trace;
  4. callable 写入它自己的结果对象;
  5. 成功后移除 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.java
  • frameworks/base/services/core/java/com/android/server/SystemConfig.java
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 实现本身承担等待和结果发布:

java
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

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:

java
@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

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

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:

java
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);
}

主线程在允许第三方代码运行前收口:

java
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

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.aconfig
  • build/make/tools/aconfig/aconfig/src/commands.rs
text
flag {
    name: "parallelize_onbootphase"
    namespace: "system_performance"
    description: "Run services' onBootPhase methods in parallel"
    bug: "459252932"
}

Aconfig 在没有 values 输入覆盖时,为声明创建默认的 disabled、read-write 状态:

rust
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

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 的并行回调完成。

java
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

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 不变量;对应实现会直接检查当前线程:

java
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

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

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 一起放进异常。

java
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 工作不要求 LooperHandler、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

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

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 三处取信息:

bash
# 输入:当前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.java

Perfetto 中应同时查看 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 对齐后,才有条件安全地增加新的并行任务。