Skip to content

SystemServer知识图谱

用真实源码入口、状态所有者和问题索引串起 Zygote、SystemServer、服务注册、应用进程创建与启动诊断。

基于android-17.0.0_r1
AndroidSystemServerZygote进程启动源码阅读

SystemServer知识图谱 ​

本文面向已经读过 Android启动全景、SystemServer.run、SystemServiceManager 和 Process.start 的读者。它不是把前面的文章再压缩一遍,也不替代某个函数的逐行分析,而是建立一套可以反复使用的源码坐标系:看到一个启动问题时,先判断状态属于哪个进程和对象,再找到写入者、消费者、生效时机以及失败后的清理位置。

全文固定在 Android 17。读完后,你应能从 ZygoteInit.main() 解释 Zygote 父进程与 system_server 子进程为何走向不同入口;从 SystemServiceManager 区分“Java 服务实例已启动”“Binder 名称已发布”和“Boot Phase 已到达”;从 ProcessList 的 startSeq 追到 ActivityThread.attach();最后根据现象选择源码入口,而不是从 SystemServer.java 第一行开始盲目搜索。

1. 四层所有权 ​

SystemServer 系列最容易混淆的不是函数名,而是不同层次都使用“启动”“注册”“就绪”这些词。先把四类所有者分开,后面的调用链才不会串错。

层次主要所有者持有的状态直接消费者典型完成点
进程孵化Zygote、ZygoteServersocket、ABI、预加载结果、fork 返回值system_server 或应用子进程父进程回到 runSelectLoop();子进程执行返回的 Runnable
服务编排SystemServer、SystemServiceManager服务实例列表、当前 Boot Phase、并行任务各 SystemServiceonStart() 返回;某阶段全部 Future.get() 返回
服务发现servicemanager、ServiceManagerBinder 服务名到 Binder 对象的映射framework API、其他系统服务、应用进程IServiceManager.addService() 完成
应用进程AMS、ProcessList、ProcessRecordpendingStart、startSeq、pid、attach 状态Zygote、ActivityThread、AMSpid 被接收并登记;应用以相同 startSeq attach

这四层之间存在依赖,但不存在一个覆盖全部含义的“ready”布尔值。例如,服务对象已经进入 mServices,并不表示它已经发布 Binder 接口;Binder 接口可以查询,也不表示服务已经收到 PHASE_BOOT_COMPLETED;Zygote 已返回 pid,也不表示应用已经完成 attachApplication()。

2. 启动主线 ​

2.1 Zygote分叉 ​

启动主线的第一个关键点是 forkSystemServer() 的返回值。

源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

相关函数:ZygoteInit.main

java
if (startSystemServer) {
    Runnable r = forkSystemServer(
            abiList, zygoteSocketName, zygoteServer);

    // 本文注:父进程得到 null;system_server 子进程得到自己的入口。
    if (r != null) {
        r.run();
        return;
    }
}

Log.i(TAG, "Accepting command socket connections");

// 本文注:Zygote 父进程继续监听应用进程创建请求。
caller = zygoteServer.runSelectLoop(abiList);

// 本文注:普通应用子进程从 select loop 提前返回并执行应用入口。
if (caller != null) {
    caller.run();
}

同一个 Java 调用点在 fork 后分成三条控制流:Zygote 父进程进入 socket 循环;system_server 子进程直接执行 handleSystemServerProcess() 返回的入口;以后创建的应用子进程则从 runSelectLoop() 提前返回。判断代码运行在哪个进程,不能只看方法所属类,必须看 fork 返回值和后续 return。

forkSystemServer() 还固定了 system_server 的身份和入口。

源码文件:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

相关函数:forkSystemServer

java
String[] args = {
        "--setuid=1000",
        "--setgid=1000",
        "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
                + "1024,1032,1065,3001,3002,3003,3005,3006,3007,3009,3010,3011,3012",
        "--capabilities=" + capabilities + "," + capabilities,
        "--nice-name=system_server",
        "--runtime-args",
        "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
        "com.android.server.SystemServer",
};

// 本文注:native fork 返回 0 的分支才是 system_server 子进程。
pid = Zygote.forkSystemServer(
        parsedArgs.mUid, parsedArgs.mGid,
        parsedArgs.mGids, parsedArgs.mRuntimeFlags,
        null, parsedArgs.mPermittedCapabilities,
        parsedArgs.mEffectiveCapabilities);

if (pid == 0) {
    if (hasSecondZygote(abiList)) {
        waitForSecondaryZygote(socketName);
    }
    zygoteServer.closeServerSocket();
    return handleSystemServerProcess(parsedArgs);
}

return null;

这里的状态所有者仍是 Zygote:它准备 UID、GID、supplementary groups、capability 和目标类。子进程首先关闭继承来的 server socket,避免 system_server 意外继续拥有 Zygote 的监听端;双 Zygote 配置下还会等待另一侧 socket 可连接。任何“system_server 已 fork”的判断,都至少要区分“native fork 成功”“子进程入口已执行”和“Java 服务已启动”三个时刻。

下面的图只表达控制流分叉,不把预加载、服务注册和应用绑定混成一条直线。

图中蓝色节点由 Zygote 持有,绿色节点属于 system_server,靛色节点属于应用进程。橙色 fork 是所有权切换点;红色节点说明 socket 清理位于 finally,不是只有正常退出才执行。

2.2 服务分组 ​

system_server 子进程进入 SystemServer.main() 后,核心编排发生在 run()。

源码文件:frameworks/base/services/java/com/android/server/SystemServer.java

相关函数:SystemServer.run

java
try {
    t.traceBegin("StartServices");
    if (SystemProperties.getBoolean(
            "sys.system_server_inherit_rt", false)) {
        Binder.setGlobalInheritRt(true);
    }

    // 本文注:四个分组表达启动编排,不等同于四个独立进程。
    startBootstrapServices(t);
    startCoreServices(t);
    startOtherServices(t);
    startApexServices(t);

    updateWatchdogTimeout(t);
    CriticalEventLog.getInstance()
            .logSystemServerStarted();
} catch (Throwable ex) {
    Slog.e("System",
            "************ Failure starting system services", ex);
    throw ex;
} finally {
    t.traceEnd();
}

四个 start*Services() 是启动顺序的组织边界,不是服务生命周期本身。具体服务可能由 SystemServiceManager.startService() 创建,也可能由静态工厂创建后再注册;某些 APEX 服务还受模块安装结果和设备特性约束。主线程上的未处理异常会被重新抛出,不会自动跳过失败服务继续进入主循环。

2.3 主循环 ​

服务启动完成后,system_server 主线程进入永久 Looper。

源码文件:frameworks/base/services/java/com/android/server/SystemServer.java

相关位置:SystemServer.run 尾部

java
// 本文注:Binder 错误回调在主要系统服务启动后才安装。
Binder.setTransactionCallback(new IBinderCallback() {
    @Override
    public void onTransactionError(
            int pid, int code, int flags, int err) {
        mActivityManagerService
                .frozenBinderTransactionDetected(
                        pid, code, flags, err);
    }
});

// 本文注:正常运行期间主线程不应从 Looper.loop() 返回。
Looper.loop();
throw new RuntimeException(
        "Main thread loop unexpectedly exited");

这段代码给出两个重要边界。第一,服务分组返回只是初始化主线结束,SystemServer 的长期消费者是主线程消息队列、Binder 线程池和各服务自有线程。第二,Looper.loop() 返回被视为不变量破坏,恢复单位不是“重进 run”,而是由 Zygote/init 侧处理 system_server 进程死亡,详见 崩溃恢复链。

3. 服务状态 ​

3.1 实例与名称 ​

SystemServiceManager 管理 Java 服务实例和生命周期回调;ServiceManager 管理跨进程可发现的 Binder 名称。两者经常在一个 onStart() 中连续出现,但不是同一张表。

源码文件:frameworks/base/services/core/java/com/android/server/SystemServiceManager.java

相关函数:startService(Class)、startService(SystemService)

前一个重载通过公开的 Context 构造函数反射创建实例;下面保留后一个重载的真实状态写入顺序。

java
public void startService(@NonNull final SystemService service) {
    // Check if already started
    String className = service.getClass().getName();
    if (mServiceClassnames.contains(className)) {
        Slog.i(TAG, "Not starting an already started service " + className);
        return;
    }

    // 本文注:先登记实例,再同步调用 onStart()。
    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");
}

mServiceClassnames 防止同一类被重复启动,mServices 是以后广播 Boot Phase 和用户生命周期事件的消费者列表。值得注意的是,源码先把实例加入列表,再调用 onStart();若 onStart() 抛异常,异常会终止当前启动主线,实例不会在这里自动从列表移除。它依赖 system_server 进程级失败恢复,而不是在同一进程内回滚所有已启动服务。

服务需要对其他进程可见时,还要主动发布 Binder 名称。

源码文件:frameworks/base/services/core/java/com/android/server/SystemService.java

相关函数:publishBinderService、publishLocalService

java
protected final void publishBinderService(
        String name, IBinder service,
        boolean allowIsolated, int dumpPriority) {
    // 本文注:跨进程消费者通过 servicemanager 名称查询。
    ServiceManager.addService(
            name, service, allowIsolated, dumpPriority);
}

protected final <T> void publishLocalService(
        Class<T> type, T service) {
    // 本文注:LocalServices 只服务于 system_server 进程内部。
    LocalServices.addService(type, service);
}

因此,“服务注册”至少有三种含义:实例进入 SystemServiceManager.mServices,Binder 对象进入 servicemanager 名称空间,本地接口进入 LocalServices。排查 getSystemService() 返回空时应看 Binder 发布链;排查 system_server 内部依赖为空时应看 LocalServices;排查 onBootPhase() 没被调用时才看 mServices 和阶段推进。

3.2 阶段推进 ​

Boot Phase 是单调递增的全局阶段,不是每个服务独立推进的状态机。

源码文件:frameworks/base/services/core/java/com/android/server/SystemServiceManager.java

相关函数:startBootPhase

java
public void startBootPhase(@NonNull TimingsTraceAndSlog t, int phase) {
    if (phase <= mCurrentPhase) {
        throw new IllegalArgumentException("Next phase must be larger than previous");
    }
    mCurrentPhase = phase;

    Slog.i(TAG, "Starting phase " + mCurrentPhase);
    try {
        t.traceBegin("OnBootPhase_" + phase);
        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);
            }
        }

        // 本文注:串行服务直接在当前线程执行。
        for (final SystemService service : serialServices) {
            long time = SystemClock.elapsedRealtime();
            t.traceBegin("OnBootPhase_" + phase + "_" + service.getClass().getName());
            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");
            t.traceEnd();
        }

        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());
            }
            // 本文注:阶段完成点仍是所有任务汇合,而不是 submit 返回。
            for (int i = 0; i < futures.length; i++) {
                try {
                    futures[i].get();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    } finally {
        t.traceEnd();
    }

    if (phase == SystemService.PHASE_BOOT_COMPLETED) {
        final long totalBootTime = SystemClock.uptimeMillis() - mRuntimeStartUptime;
        t.logDuration("TotalBootTime", totalBootTime);
        shutdownInitThreadPool();
    }
}

这段源码同时展示了 trace、耗时告警和异常包装。mCurrentPhase 先更新,再向服务分发;阶段号倒退或重复会立即失败。并行开关开启后,服务可以通过 getBootPhaseSerial() 留在串行组,其他服务才进入初始化线程池。Future.get() 是本阶段的提交屏障:后续阶段不能在并行回调仍运行时开始。到 PHASE_BOOT_COMPLETED 后线程池被关闭,所以不能把它当作系统运行期的通用执行器。

图中主线程拥有阶段顺序,服务实例拥有各自业务状态,线程池只拥有任务执行。并行化改变回调运行线程,却没有改变阶段完成的消费者:SystemServer 仍要等 startBootPhase() 返回。

3.3 失败边界 ​

失败点当时已写入的状态处理方式不应误判为
服务构造失败尚未加入 mServices包装为 RuntimeException,终止启动主线服务被忽略后继续启动
onStart() 抛异常类名和实例已加入管理列表重新抛出,通常进入进程级恢复自动回滚服务注册
Boot Phase 倒退mCurrentPhase 未改写IllegalArgumentException忽略重复阶段
串行回调异常mCurrentPhase 已推进包装后抛出继续通知剩余服务
并行回调异常任务已提交Future.get() 将异常带回主线程后台静默失败
Binder 发布失败Java 实例可能已启动ServiceManager.addService() 记录远端异常生命周期和服务发现同时成功

这张表说明服务启动缺少跨服务事务式回滚。关键服务初始化失败时,Android 依靠 system_server 的进程重启重建整体状态;服务自身若持有外部资源,仍应在内部失败路径中及时关闭文件、线程或 native handle,不能假设进程一定立刻退出。

4. 进程主线 ​

4.1 待启动状态 ​

应用进程启动不是“AMS 调用一次 fork”这么简单。AMS 侧先创建一个带序号的待完成事务,Zygote 返回 pid 后再验证这次返回是否仍对应当前 ProcessRecord。

源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java

相关函数:startProcessLocked

java
boolean startProcessLocked(
        HostingRecord hostingRecord, String entryPoint,
        ProcessRecord app, int uid, int[] gids,
        int runtimeFlags, int zygotePolicyFlags,
        int mountExternal, String seInfo,
        String requiredAbi, String instructionSet,
        String invokeWith, long startUptime,
        long startElapsedTime) {
    app.setPendingStart(true);
    app.setRemoved(false);

    // ... 查询 compat change,并记录 wrapper 等启动参数。
    final long startSeq = ++mProcStartSeqCounter;
    app.setStartSeq(startSeq);
    app.setStartParams(uid, hostingRecord, seInfo,
            startUptime, startElapsedTime);
    mPendingStarts.put(startSeq, app);

    if (mService.mConstants.FLAG_PROCESS_START_ASYNC) {
        // 本文注:异步路径释放 AMS 全局锁后再访问 Zygote。
        mService.mProcStartHandler.post(() ->
                handleProcessStart(app, entryPoint, gids,
                        runtimeFlags, zygotePolicyFlags,
                        mountExternal, requiredAbi,
                        instructionSet, invokeWith, startSeq));
        return true;
    }

    try {
        Process.ProcessStartResult result =
                startProcess(hostingRecord, entryPoint, app,
                        uid, gids, runtimeFlags,
                        zygotePolicyFlags, mountExternal,
                        seInfo, requiredAbi, instructionSet,
                        invokeWith, startUptime);
        handleProcessStartedLocked(app, result.pid,
                result.usingWrapper, startSeq, false);
    } catch (RuntimeException e) {
        app.setPendingStart(false);
        mService.forceStopPackageLocked(
                app.info.packageName,
                UserHandle.getAppId(app.uid),
                false, false, true, false, false, false,
                app.userId, "start failure");
    }
    return app.getPid() > 0;
}

startSeq 是一次启动尝试的身份,不是 pid 的替代品。pid 可能复用,异步返回也可能晚于一次新的启动尝试;mPendingStarts 用 startSeq 找回 owner,pendingStart 表示事务尚未完成。异步开关开启时,方法返回 true 只表示任务已投递,不表示 fork 已完成,更不表示应用已经 attach。

Zygote 返回后,handleProcessStartedLocked() 先消费待启动记录,再决定是否接受 pid。

源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java

相关函数:handleProcessStartedLocked

java
boolean handleProcessStartedLocked(
        ProcessRecord app, int pid, boolean usingWrapper,
        long expectedStartSeq, boolean procAttached) {
    mPendingStarts.remove(expectedStartSeq);

    String reason =
            isProcStartValidLocked(app, expectedStartSeq);
    if (reason != null) {
        // 本文注:过期启动返回的 pid 不能覆盖更新后的 ProcessRecord。
        app.setPendingStart(false);
        killProcessQuiet(pid);
        final int appPid = app.getPid();
        if (appPid != 0) {
            Process.killProcessGroup(app.uid, appPid);
        }
        noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
                ApplicationExitInfo.SUBREASON_INVALID_START, reason);
        app.doEarlyCleanupIfNecessaryLocked();
        return false;
    }

    // ... 记录 BatteryStats、EventLog、PMS 和 Watchdog 启动信息。
    synchronized (mProcLock) {
        app.setPid(pid);
        app.setUsingWrapper(usingWrapper);
        app.setPendingStart(false);
    }
    mService.addPidLocked(app);

    synchronized (mService.mPidsSelfLocked) {
        if (!procAttached) {
            Message msg = mService.mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
            msg.obj = app;
            mService.mHandler.sendMessageDelayed(msg, usingWrapper
                    ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
        }
    }
    dispatchProcessStarted(app, pid);
    return true;
}

正常消费者是 AMS 的 pid 映射和后续 attach;过期结果的消费者则是清理路径。这里体现了一个重要不变量:只有 expectedStartSeq 仍匹配当前启动尝试,pid 才能写回 ProcessRecord。否则系统宁愿杀掉新 fork 的进程,也不能把它错误绑定到旧记录。

4.2 参数协议 ​

ProcessList 把安全身份、挂载模式、ABI、SELinux 信息和 startSeq 传给 Process.start()。

源码文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java

相关位置:startProcess 的 regular Zygote 分支

java
startResult = Process.start(
        entryPoint,
        app.processName,
        uid,
        uid,
        gids,
        runtimeFlags,
        mountExternal,
        app.info.targetSdkVersion,
        seInfo,
        requiredAbi,
        instructionSet,
        app.info.dataDir,
        invokeWith,
        app.info.packageName,
        zygotePolicyFlags,
        isTopApp,
        app.getDisabledCompatChanges(),
        app.getEnabledCompatChanges(),
        useDeliQueue,
        pkgDataInfoMap,
        allowlistedAppDataInfoMap,
        bindMountAppsData,
        bindMountAppStorageDirs,
        bindOverrideSysprops,
        app.getStartSeq(),
        new String[]{PROC_START_SEQ_IDENT
                + app.getStartSeq()});

这些参数不是装饰性元数据。UID/GID 和 supplementary groups 决定 Linux 身份;seInfo 参与 SELinux domain 选择;ABI 决定连接主 Zygote 还是次 Zygote;mountExternal 和 bind-mount 参数决定可见文件系统;startSeq 最终回到 AMS 完成身份校验。修改其中一个字段时,必须继续追踪 Zygote 参数解析和 native specialization,而不是只改 Java 调用签名。

ZygoteProcess.startViaZygote() 将结构化参数编码为换行协议。

源码文件:frameworks/base/core/java/android/os/ZygoteProcess.java

相关函数:startViaZygote、zygoteSendArgsAndGetResult

java
ArrayList<String> argsForZygote = new ArrayList<>();

// 本文注:身份与运行参数位于协议前部。
argsForZygote.add("--runtime-args");
argsForZygote.add("--setuid=" + uid);
argsForZygote.add("--setgid=" + gid);
argsForZygote.add("--runtime-flags=" + runtimeFlags);

// ... 根据 mountExternal 添加对应的挂载模式参数。
argsForZygote.add("--target-sdk-version="
        + targetSdkVersion);

if (gids != null && gids.length > 0) {
    final StringBuilder sb = new StringBuilder();
    sb.append("--setgroups=");

    final int sz = gids.length;
    for (int i = 0; i < sz; i++) {
        if (i != 0) {
            sb.append(',');
        }
        sb.append(gids[i]);
    }

    argsForZygote.add(sb.toString());
}
if (niceName != null) {
    argsForZygote.add("--nice-name=" + niceName);
}
if (seInfo != null) {
    argsForZygote.add("--seinfo=" + seInfo);
}
if (instructionSet != null) {
    argsForZygote.add("--instruction-set="
            + instructionSet);
}
if (appDataDir != null) {
    argsForZygote.add("--app-data-dir=" + appDataDir);
}

argsForZygote.add(processClass);
if (extraArgs != null) {
    Collections.addAll(argsForZygote, extraArgs);
}

上面保留了参数类别、顺序和 supplementary groups 的真实构造过程。真正写 socket 前还有一次完整校验。

源码文件:frameworks/base/core/java/android/os/ZygoteProcess.java

相关函数:zygoteSendArgsAndGetResult

java
for (String arg : args) {
    // 本文注:先验证整个列表,避免只发送半条命令。
    if (arg.indexOf('\n') >= 0) {
        throw new ZygoteStartFailedEx(
                "Embedded newlines not allowed");
    } else if (arg.indexOf('\r') >= 0) {
        throw new ZygoteStartFailedEx(
                "Embedded carriage returns not allowed");
    } else if (arg.indexOf('\u0000') >= 0) {
        throw new ZygoteStartFailedEx(
                "Embedded nulls not allowed");
    }
}

// 本文注:第一行是参数数量,后续每行一个参数。
String msgStr = args.size() + "\n"
        + String.join("\n", args) + "\n";

换行、回车和 NUL 会破坏帧边界,所以校验发生在任何写入之前。这是协议的失败原子性:编码失败不会让 Zygote 收到一个参数计数正确、内容却不完整的请求。socket I/O 失败则属于另一层,调用方会将其包装为进程启动失败并清理 mPendingStarts。

4.3 attach闭环 ​

应用子进程从 RuntimeInit.applicationInit() 找到 ActivityThread.main()。startSeq 作为普通命令行参数进入应用进程,并在 attach 时原样带回 AMS。

源码文件:frameworks/base/core/java/android/app/ActivityThread.java

相关函数:main、attach

java
long startSeq = 0;
if (args != null) {
    for (int i = args.length - 1; i >= 0; --i) {
        if (args[i] != null
                && args[i].startsWith(PROC_START_SEQ_IDENT)) {
            startSeq = Long.parseLong(args[i].substring(
                    PROC_START_SEQ_IDENT.length()));
        }
    }
}

Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

Looper.loop();
throw new RuntimeException(
        "Main thread loop unexpectedly exited");

main() 只负责建立应用主线程和发起 attach;真正跨进程交回 AMS 的代码位于 attach(false, startSeq)。

源码文件:frameworks/base/core/java/android/app/ActivityThread.java

相关函数:attach

java
private void attach(boolean system, long startSeq) {
    sCurrentActivityThread = this;
    mSystemThread = system;
    mStartSeq = startSeq;

    if (!system) {
        RuntimeInit.setApplicationObject(
                mAppThread.asBinder());
        IActivityManager mgr =
                ActivityManager.getService();
        try {
            // 本文注:Binder 调用把应用线程接口和启动序号交回 AMS。
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
}

ActivityThread 是应用进程内的状态所有者,mAppThread 是 system_server 回调应用进程的 Binder 入口。AMS 消费 startSeq 时还会同时读取 Binder calling pid 和 uid。

源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

相关函数:attachApplicationLocked

java
ProcessRecord app;
synchronized (mPidsSelfLocked) {
    app = mPidsSelfLocked.get(pid);
}

if (app != null
        && (app.getStartUid() != callingUid
                || app.getStartSeq() != startSeq)) {
    // 本文注:pid 已被其他启动占用时,先清理旧映射。
    cleanUpApplicationRecordLocked(
            app, pid, false, false, -1,
            true, false);
    removePidLocked(pid, app);
    app = null;
}

if (app == null && startSeq > 0) {
    ProcessRecord pending =
            mProcessList.mPendingStarts.get(startSeq);
    if (pending != null
            && pending.getStartUid() == callingUid
            && pending.getStartSeq() == startSeq
            && mProcessList.handleProcessStartedLocked(
                    pending, pid, pending.isUsingWrapper(),
                    startSeq, true)) {
        app = pending;
    }
}

if (app == null) {
    EventLogTags.writeAmDropProcess(pid);
    killProcessQuiet(pid);
    return;
}

这段逻辑覆盖“应用 attach 比 system_server 处理 fork 返回更快”的竞态:如果 pid map 尚未建立,AMS 可以用 startSeq 从 mPendingStarts 找回 ProcessRecord 并补做登记。反方向上,如果 pid、uid 或序号不一致,进程会被丢弃,防止错误的应用线程绑定到另一个记录。

图中的关键并不是方法数量,而是同一个 startSeq 被三个进程共同携带:system_server 创建它,Zygote 透明传递它,应用进程再交还它。它让 AMS 能把异步 fork 结果、pid 映射和 Binder attach 归并为同一次启动尝试。

4.4 失败清理 ​

场景检测位置清理动作恢复单位
参数含换行、回车或 NULZygoteProcess 写 socket 前不发送请求,抛出启动异常当前启动尝试
socket 或 fork 失败ProcessList.handleProcessStart()删除 mPendingStarts、清除 pendingStart、force-stop、early cleanup包或进程启动请求
迟到的 fork 结果isProcStartValidLocked()杀新 pid、记录退出原因、early cleanup当前 pid
pid 被旧记录占用attachApplicationLocked()清理旧 ProcessRecord 和 pid mappid 映射
attach 无匹配记录attachApplicationLocked()写 am_drop_process 并杀进程当前应用进程
bind 完成超时AMS timeout handler杀掉卡住的 ProcessRecord当前应用进程

这一链路没有“收到 pid 就成功”的单一完成点。至少要观察 Zygote 返回、AMS 接受 pid、应用 attach、bindApplication 执行和最终超时消息移除。需要逐步跟踪参数和 Binder 回调时,可继续阅读 Zygote参数传递协议、子进程PID管理 和 handleBindApplication。

5. 故障坐标 ​

下面的关系图把“现象”连接到第一个值得检查的 owner。它不是自动诊断器,而是避免一上来就在错误进程里搜日志。

把图转换成源码搜索表,可以得到更具体的入口:

现象第一入口状态 owner下一消费者关键边界
StartServices 很长SystemServer.run()SystemServer 主线程四个服务分组trace slice 可能不等待服务内部异步任务
某服务 onStart() 已运行但客户端查不到publishBinderService()服务实例与 servicemanagerServiceManager.getService()Java 生命周期与 Binder 发布是两次操作
Boot Phase 卡住startBootPhase()mCurrentPhase、Future 数组下一阶段调用方并行任务仍由 Future.get() 汇合
应用启动一直 pendingstartProcessLocked()ProcessRecord、mPendingStartsZygote socket异步投递成功不等于 fork 成功
有 pid 但 attach 被拒绝attachApplicationLocked()AMS pid mapbindApplicationpid、uid、startSeq 必须同时匹配
system_server 崩溃后重启ZygoteInit 与 init serviceZygote/init新 system_server恢复单位是进程,不是单个服务
首次启动明显更慢isFirstBootOrUpgrade()、PMSPackageManagerService启动统计与 dexopt不能和普通启动样本直接比较
多用户切换后进程异常UserController、SSM user callbacks用户状态与 per-user 进程各系统服务user 事件与全局 Boot Phase 是两套生命周期
RSS/PSS 持续增长dumpsys meminfo、heap/native ownersystem_server heap、native allocatorGC、服务缓存、native 组件单次 PSS 不能证明泄漏

6. 测试边界 ​

6.1 服务注册测试 ​

源码文件:frameworks/base/services/tests/servicestests/src/com/android/server/SystemServiceManagerTest.java。

java
@Test
public void testDuplicateServices() throws Exception {
    AtomicInteger counter = new AtomicInteger(0);
    SystemService service = new SystemService(getContext()) {
        @Override
        public void onStart() {
            counter.incrementAndGet();
        }
    };

    mSystemServiceManager.startService(service);
    assertEquals(1, counter.get());

    // 本文注:第二次提交同一服务类不能再次执行 onStart()。
    mSystemServiceManager.startService(service);
    assertEquals(1, counter.get());
}

@Test
public void testSealStartedServices() throws Exception {
    // ... 构造 service1 和 service2;service2.onStart() 若被调用会主动失败。
    mSystemServiceManager.startService(service1);
    assertTrue(serviceStarted.get());

    mSystemServiceManager.sealStartedServices();
    assertThrows(UnsupportedOperationException.class,
            () -> mSystemServiceManager.startService(service2));
}

第一个测试的输入是同一个服务实例被提交两次,关键断言是 onStart() 计数仍为 1,它验证类名集合确实承担去重职责。第二个测试先启动一个服务,再封存列表并尝试新增服务,关键断言是抛出 UnsupportedOperationException;它验证封存后的列表不再接受写入。两者都没有覆盖 Boot Phase 并行任务、Binder 名称发布或 onStart() 失败后的进程恢复,不能据此推导这些路径也正确。

6.2 进程超时测试 ​

源码文件:frameworks/base/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java。

java
@Test
public void testNormal() throws Exception {
    if (mRealAms.mConstants
            .mEnableWaitForFinishAttachApplication) {
        ProcessRecord app =
                startProcessAndWait(false);
        verify(app, never())
                .killLocked(any(), anyInt(), anyBoolean());
    }
}

@Test
public void testWedged() throws Exception {
    ProcessRecord app = startProcessAndWait(true);

    verify(app).killLocked(
            any(), anyInt(), anyBoolean());
}

两个测试共同调用真实测试辅助函数 startProcessAndWait():它先让 ProcessList 接受 pid,再调用测试 IApplicationThread.bindApplication();wedge=false 时测试线程回报 finish-attach,wedge=true 时等待超过 PROC_START_TIMEOUT。关键断言分别是正常进程不被杀、超时进程必须被杀。它验证了“fork/pid 已完成仍不代表启动完成”,以及超时消息的消费者确实执行清理;但它没有经过真实 Zygote socket、native fork 或设备调度,因此不能用来衡量真实启动耗时。

6.3 参数解析测试 ​

源码文件:frameworks/base/core/tests/coretests/src/com/android/internal/os/ZygoteArgumentsTest.java。

java
@Test
public void testParseAndMergeCompatChanges_dedup() {
    long[] changes = {1, 2};

    changes = ZygoteArguments.parseAndMergeCompatChanges(
            "--enabled-compat-changes=2,3,3,4",
            changes);

    long[] expected = {1, 2, 3, 4};
    assertArrayEquals(expected, changes);
}

@Test
public void testParseAndMergeCompatChanges_malformed() {
    long[] changes = null;

    changes = ZygoteArguments.parseAndMergeCompatChanges(
            "--disabled-compat-changes=1,,2",
            changes);

    long[] expected = {1, 2};
    assertArrayEquals(expected, changes);
}

这组测试验证 compat change 参数在多次输入下会排序、合并和去重,并允许空片段;它说明 Zygote 参数不是简单字符串透传,而是会形成结构化状态。测试没有覆盖 socket 帧校验、UID/GID 权限限制和 native specialization,阅读参数链时仍要继续跟到 ZygoteArguments.getInstance() 与 Zygote.nativeForkAndSpecialize()。

7. 全篇索引 ​

索引按问题所有者分组。每个链接的可见名称都与文章标题一致;编号只留在目标文件名中,不要求读者记忆。

7.1 Zygote入口 ​

阅读目的文章
建立全局进程图Android启动全景
找到 native 入口app_process入口、AndroidRuntime.start
进入 Java 主线ZygoteInit.main、Zygote socket注册
理解共享预加载Zygote预加载、preloadClasses、preloadResources、Zygote fork准备
理解双进程与请求循环Zygote双进程、ZygoteServer循环、Zygote安全限制

7.2 SystemServer入口 ​

阅读目的文章
跟踪 fork 后接管forkSystemServer、SystemServer接管、zygoteInit与RuntimeInit
跟踪 Java 启动骨架SystemServer.main、SystemServer.run、createSystemContext
分解服务分组Bootstrap服务、Core服务、Other服务、APEX服务

7.3 服务编排 ​

阅读目的文章
区分名称注册与实例管理ServiceManager.addService、SystemServiceManager
理解生命周期和阶段SystemService生命周期、BootPhase阶段
理解存活监控和顺序Watchdog监控、核心服务顺序、AMS/WMS/PMS时序
分析依赖与耗时服务依赖关系、服务启动耗时、Trampoline模式

7.4 应用进程 ​

阅读目的文章
进入创建请求Process.start、ZygoteProcess.start
理解 fork 策略USAP池、fork与COW、进程specialization、ABI进程选择
进入应用 Java 世界RuntimeInit.applicationInit、ActivityThread.main、handleBindApplication、Application.onCreate

7.5 协议与隔离 ​

阅读目的文章
跟踪 socket 请求LocalSocket与Zygote通信、ZygoteConnection.processCommand、Zygote参数传递协议
跟踪 pid 生死子进程PID管理、SIGCHLD进程死亡处理
理解安全身份进程UID/GID设置、进程Capability设置、Seccomp沙箱

7.6 启动优化 ​

阅读目的文章
建立时间线启动时间分析、开机动画时序、启动场景判定
选择优化手段预加载优化、并行启动、延迟启动

7.7 运行诊断 ​

阅读目的文章
处理进程级故障崩溃恢复链
处理环境差异多用户进程、容器化启动
处理资源与性能SystemServer内存、SystemServer分析

8. 问题入口 ​

你要回答的问题建议起点继续深入不要先做什么
哪个阶段拖慢开机启动时间分析并行启动、SystemServer分析先改 preload 列表
某系统服务为什么没起来SystemServiceManagerSystemService生命周期、服务依赖关系只搜 addService 字符串
应用为什么没有 pidProcess.startZygoteProcess.start、Zygote参数传递协议直接从 Application.onCreate() 倒推
有 pid 为什么没进入应用ActivityThread.mainhandleBindApplication、子进程PID管理把 fork 返回当成启动完成
system_server 为什么反复重启崩溃恢复链Watchdog监控、SIGCHLD进程死亡处理只查看最后一次 Java exception
用户切换后哪些服务被通知多用户进程SystemService生命周期把用户事件当作 Boot Phase
Cuttlefish 中 Zygote 有何差异容器化启动进程Capability设置、进程specialization假设宿主 capability 全部可用
system_server RSS/PSS 为什么增长SystemServer内存SystemServer分析用一份 meminfo 直接下泄漏结论
首次启动为何无法复现普通启动启动场景判定预加载优化混合 first boot、OTA 和 runtime restart 样本

9. 源码导航 ​

以下命令都在 AOSP 根目录执行。注释先说明问题,再给出搜索范围,避免全仓搜索产生大量同名符号。

bash
# 找到 SystemServer 服务分组、阶段推进和最终主循环。
rg -n 'start(Bootstrap|Core|Other|Apex)Services|startBootPhase|Looper\.loop' \
  frameworks/base/services/java/com/android/server/SystemServer.java \
  frameworks/base/services/core/java/com/android/server/SystemServiceManager.java

若问题发生在 fork 前后,再把搜索范围切换到 Zygote 入口和请求循环:

bash
# 找到 system_server fork 的父子分支和应用请求循环。
rg -n 'forkSystemServer|handleSystemServerProcess|runSelectLoop' \
  frameworks/base/core/java/com/android/internal/os/ZygoteInit.java \
  frameworks/base/core/java/com/android/internal/os/ZygoteServer.java

若现象是应用进程迟迟不能完成启动,先追踪 AMS 持有的启动序号:

bash
# 跟踪同一个 startSeq 如何从 ProcessList 进入应用,再返回 AMS。
rg -n 'mProcStartSeqCounter|mPendingStarts|PROC_START_SEQ_IDENT|attachApplication' \
  frameworks/base/services/core/java/com/android/server/am/ProcessList.java \
  frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java \
  frameworks/base/core/java/android/app/ActivityThread.java

确认 startSeq 主线后,再检查它与安全身份如何一起编码和解析:

bash
# 查找 Zygote 参数编码、解析和 native specialization 的边界。
rg -n 'startViaZygote|zygoteSendArgsAndGetResult|getInstance|nativeForkAndSpecialize' \
  frameworks/base/core/java/android/os/ZygoteProcess.java \
  frameworks/base/core/java/com/android/internal/os/ZygoteArguments.java \
  frameworks/base/core/java/com/android/internal/os/Zygote.java

完成静态阅读后,可以用对应测试检查服务去重和 attach 超时边界:

bash
# 运行前先确认设备或测试环境支持对应模块;两个测试分别覆盖服务注册和启动超时。
atest SystemServiceManagerTest AsyncProcessStartTest

命令输出的函数位置只是入口。真正阅读时,应继续回答四个问题:谁写状态、在哪把状态交给另一个进程或线程、哪个条件使它生效、失败时由谁清理。

10. 闭环复述 ​

  1. 从 ZygoteInit.main() 开始,分别复述父 Zygote、system_server 子进程和普通应用子进程的返回值与下一入口,并指出每条路径关闭哪个 socket。
  2. 选择一个 SystemService,分别找到它进入 mServices、执行 onStart()、发布 Binder 或 LocalService、接收关键 Boot Phase 的位置。若四个位置不全,说明缺少的是哪一种能力。
  3. 从 ProcessList.startProcessLocked() 的 startSeq 出发,追到 ZygoteProcess 参数列表、ActivityThread.main() 参数解析和 ActivityManagerService.attachApplicationLocked() 校验。然后解释 pid 复用为什么不能只靠 pid 识别启动尝试。
  4. 给定“开机慢但 CPU 不高”的现象,先用 启动时间分析 找长阶段,再检查该阶段是否等待 Future.get()、Binder 返回或锁;只有确认 owner 后,才选择 SystemServer分析 中的 Perfetto、BinderCallsStats 或 LooperStats。

这四条主线能完整复述后,SystemServer 就不再是一份超长启动函数,而是一组边界清楚的状态交接:Zygote 交出进程控制权,SystemServiceManager 交出生命周期阶段,servicemanager 交出跨进程可发现性,ProcessList 用 startSeq 把异步 fork 和应用 attach 收拢为同一次启动事务。以后遇到新服务、新启动 flag 或新的进程类型,先把它放回这套所有权坐标,再沿真实调用方和消费者向两端扩展。