Skip to content

多用户进程

从 UserController 的用户启动到 ProcessList 和 Zygote 的 UID、SELinux 与数据隔离,追踪多用户进程的生命周期。

基于android-17.0.0_r1
AndroidSystemServerActivityManagerUserManagerZygote多用户源码阅读

多用户进程 ​

本文面向已经读过 SystemServer 服务启动、应用进程创建 和 Zygote 参数协议 的读者。本文不重复 Activity 切换或 PackageManager 的安装细节,而是回答一个跨层问题:用户 10 从“未运行”变成“可运行”时,UserController、UserManagerService、ProcessList、UserHandle 和 Zygote 各自改变了什么;停止用户时,进程、CE/DE 数据和生命周期广播按什么顺序收束。对 UserController 的用户生命周期背景,本文会在正文中补足必要的 UserState 和 profile 关系。

Android 的多用户隔离不是“每个用户一个 Zygote”。所有普通应用仍由同一组 Zygote fork;隔离来自组合约束:UID 使用 userId * PER_USER_RANGE + appId 编码,ProcessRecord.userId 与 uid 绑定,Zygote specialization 接收最终 UID/GID、mount mode 和 seInfo,而用户启动状态由 system_server 的 UserState 与 UserManager 的状态表共同维护。读完本文后,应该能从一次 startUserInBackground() 追到用户状态、DE/CE 存储、生命周期回调和第一个应用进程的启动参数,并能解释为什么停止一个用户不会简单等于杀掉所有同名进程。

1. 隔离模型 ​

先区分三个 ID:userId 是用户/资料的逻辑编号,appId 是 PackageManager 为应用分配的基础编号,Linux 进程真正使用的是组合后的 uid。例如 user 10 的 appId 10001 组合成 1010001;同一个 APK 在 user 0 和 user 10 中拥有不同 Linux UID,因此 /data/user/0 与 /data/user/10 的访问边界可以落到内核权限和 SELinux 策略上。

图中 userId 只在 system_server 的用户编排和 PackageManager 查询中使用;Zygote 接收的是已经组合好的 UID,以及由包信息派生出的 seInfo。因此排查“应用跑到了错误用户”时,不能只看进程名:同名进程要用 (processName, uid) 作为键。

对象owner主要状态生效时机
UserController.mStartedUsersActivityManagerUserState、用户是否允许执行代码加入 map 后开始启动流程
UserManagerService.mUserStatesUserManager对其他服务公开的用户状态setUserState() 更新后
ProcessRecordProcessList/AMSuid、userId、PID、startSeq创建或重启进程时
CE/DE 用户存储vold/UserDataPreparerDE 可用、CE 是否解锁onBeforeStartUser / onBeforeUnlockUser
mUserLruUserController运行用户淘汰顺序每次 start 请求后移动到末尾

2. UID编码 ​

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

相关常量与函数:PER_USER_RANGE、getUserId、getUid、getAppId

java
public static final int PER_USER_RANGE = 100000;

public static @UserIdInt int getUserId(int uid) {
    if (MU_ENABLED) {
        return uid / PER_USER_RANGE;
    } else {
        return UserHandle.USER_SYSTEM;
    }
}

public static int getUid(@UserIdInt int userId,
        @AppIdInt int appId) {
    if (MU_ENABLED && appId >= 0) {
        // 本文注:appId 只取每用户范围内的低位部分。
        return userId * PER_USER_RANGE
                + (appId % PER_USER_RANGE);
    } else {
        return appId;
    }
}

public static @AppIdInt int getAppId(int uid) {
    return uid % PER_USER_RANGE;
}

PER_USER_RANGE 是编码协议,不是可随意替换的乘数。getUserId() 和 getAppId() 必须互相匹配,否则 AMS 的 (processName, uid) 查找、PackageManager 的按用户查询和 kernel 的 UID 权限都会出现不同解释。MU_ENABLED=false 是单用户兼容分支;在 Android 17 的通常构建中它为 true,但阅读代码时仍要看到这个条件,不能把除法写成无条件行为。

3. 启动入口 ​

公开调用通常从 ActivityManagerService 的 Binder 方法进入,再转交 UserController。权限检查位于 UserController,而不是让每个下游服务自行重新判断。

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

相关函数:startUserInBackgroundWithListener、startUserForegroundWithListener、switchUser

java
@Override
public boolean startUserInBackgroundWithListener(
        final int userId,
        @Nullable IProgressListener unlockListener) {
    // 本文注:Binder 入口只选择模式,具体权限和状态检查下沉到 UserController。
    return mUserController.startUser(
            userId, USER_START_MODE_BACKGROUND,
            unlockListener);
}

@Override
public boolean startUserForegroundWithListener(
        final int userId,
        @Nullable IProgressListener unlockListener) {
    return mUserController.startUser(
            userId, USER_START_MODE_FOREGROUND,
            unlockListener);
}

@Override
public boolean switchUser(final int targetUserId) {
    return mUserController.switchUser(targetUserId);
}

入口返回 boolean 只表示启动请求是否被接受/完成,不能解释为用户已经解锁。unlockListener 的完成时机由后面的 finishUserUnlocking() 决定;foreground 模式还要等待切换 UI、旧用户收束和新用户广播。

4. 启动状态 ​

UserController.startUserInternal() 同时维护当前用户、目标用户、已启动用户表和 LRU。它先验证 system_server 已经进入可启动用户的阶段,再处理显示器、profile、已有状态和用户分配。

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

相关函数:startUserInternal

java
private boolean startUserInternal(@UserIdInt int userId,
        int displayId, @UserStartMode int userStartMode,
        int autoStopUserInSecs,
        @Nullable IProgressListener unlockListener,
        TimingsTraceAndSlog t) {
    synchronized (mLock) {
        // 本文注:mReady 是用户生命周期的总闸门。
        Preconditions.checkState(
                mReady,
                EXCEPTION_TEMPLATE_CANNOT_START_USER_WHEN_NOT_READY,
                userId);
    }

    boolean foreground =
            userStartMode == USER_START_MODE_FOREGROUND;
    boolean onSecondaryDisplay =
            displayId != Display.DEFAULT_DISPLAY;
    if (onSecondaryDisplay) {
        Preconditions.checkArgument(!foreground,
                "Cannot start user %d in foreground AND on secondary display (%d)",
                userId, displayId);
    }
    if (autoStopUserInSecs > 0) {
        Preconditions.checkArgument(
                userStartMode == USER_START_MODE_BACKGROUND,
                "Cannot auto-stop a non-bg (%d) user %d in %d s",
                userStartMode, userId, autoStopUserInSecs);
    }

    final UserInfo userInfo = getUserInfo(userId);
    if (userInfo == null) {
        Slogf.w(TAG, "No user info for user #" + userId);
        return false;
    }
    if (foreground && userInfo.isProfile()) {
        Slogf.w(TAG,
                "Cannot switch to User #" + userId
                        + ": not a full user");
        return false;
    }

    // ... assignUserToDisplayOnStart() 和 display/profile 校验。
    // ... 进入 updateStartedUserArrayStarting。
}

这里的失败不是异常恢复:mReady=false 直接抛出前置条件异常,未知用户和 profile 作为 foreground 返回 false。displayId 与 userStartMode 的组合也有约束,background-visible 才能绑定 secondary display。把所有 start 都理解成“切换到前台”会遗漏后台用户和车载/多显示器场景。

mStartedUsers 的插入是状态提交点之一。

java
// UserController.startUserInternal(),updateStartedUserArrayStarting 区域
synchronized (mLock) {
    uss = mStartedUsers.get(userId);
    if (uss == null) {
        uss = new UserState(UserHandle.of(userId));
        uss.mUnlockProgress.addListener(
                new UserProgressListener());
        mStartedUsers.put(userId, uss);
        updateStartedUserArrayLU();
        needStart = true;
        updateUmState = true;
    } else if (uss.state == UserState.STATE_SHUTDOWN
            || mDoNotAbortShutdownUserIds.contains(userId)) {
        // 本文注:正在 shutdown 的用户进入 pending,而不是并发启动第二套状态机。
        mPendingUserStarts.add(new PendingUserStart(
                userId, userStartMode, autoStopUserInSecs,
                unlockListener));
        return true;
    }
}

addUserToUserLru(userId);
if (updateUmState) {
    mInjector.getUserManagerInternal()
            .setUserState(userId, uss.state);
}

UserState 由 mLock 保护,mUserLru 也必须在同一把锁下更新。用户已经处于 STATE_RUNNING_UNLOCKED 时,调用方得到完成通知并提前返回;如果处于 STATE_SHUTDOWN,请求被排队到完整 shutdown 之后。这个分支保证了“一个 userId 同时只有一份生命周期状态”。

5. 启动时序 ​

创建 UserState 后,foreground 模式会异步经过切换前回调;background 模式可直接继续。真正的系统服务和应用广播按 Handler 顺序进入队列。

图中 prepareUserData(DE) 早于应用进程启动,保证进程第一次访问设备加密数据时目录和权限已经准备;CE 解锁则是另一条路径,不应被 STATE_RUNNING_LOCKED 隐含掉。finishUserBoot() 先完成用户生命周期状态,再由 maybeUnlockUser() 处理 credential-protected storage。

6. UserManager协作 ​

UserManagerService 是系统服务回调和用户数据准备的 owner。UserController 不直接实现所有存储迁移,而是在用户即将启动/解锁前调用 UserManagerInternal。

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

相关函数:Lifecycle.onBootPhase、onBeforeStartUser、onBeforeUnlockUser

java
public void onBootPhase(int phase) {
    mUms.mCurrentBootPhase = phase;
    if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
        mUms.cleanupPartialUsers();
        if (mUms.mPm.isDeviceUpgrading()) {
            mUms.cleanupPreCreatedUsers();
        }
        mUms.registerStatsCallbacks();
    }
}

public void onBeforeStartUser(@UserIdInt int userId) {
    UserInfo userInfo = getUserInfo(userId);
    if (userInfo == null) return;

    boolean migrateAppsData =
            !PackagePartitions.FINGERPRINT.equals(
                    userInfo.lastLoggedInFingerprint);
    mUserDataPreparer.prepareUserData(
            userInfo, StorageManager.FLAG_STORAGE_DE);
    getPackageManagerInternal().reconcileAppsData(
            userId, StorageManager.FLAG_STORAGE_DE,
            migrateAppsData);

    if (userId != UserHandle.USER_SYSTEM) {
        synchronized (mRestrictionsLock) {
            applyUserRestrictionsLR(userId);
        }
    }
}

PHASE_ACTIVITY_MANAGER_READY 的清理发生在用户可以被正常启动之前,处理上次创建用户中断留下的 partial/pre-created 状态。onBeforeStartUser() 的 DE 准备对有无锁屏凭据都适用;migrateAppsData 只在该用户上次登录 fingerprint 与当前分区 fingerprint 不同时打开。它不是全局 OTA 开关。

解锁阶段准备 CE,并通知 StorageManager 该用户的 CE 已可用。

java
public void onBeforeUnlockUser(@UserIdInt int userId) {
    UserInfo userInfo = getUserInfo(userId);
    if (userInfo == null) return;

    boolean migrateAppsData =
            !PackagePartitions.FINGERPRINT.equals(
                    userInfo.lastLoggedInFingerprint);
    mUserDataPreparer.prepareUserData(
            userInfo, StorageManager.FLAG_STORAGE_CE);
    getStorageManagerInternal()
            .markCeStoragePrepared(userId);
    getPackageManagerInternal().reconcileAppsData(
            userId, StorageManager.FLAG_STORAGE_CE,
            migrateAppsData);
    // ... 继续执行 CE 准备后的用户权限和锁定状态同步。
}

markCeStoragePrepared() 是生效边界:在它之前,CE 数据对依赖该状态的消费者仍不可用。若用户尚未启动,maybeUnlockUser() 可以尝试解锁密钥,但随后会因为 mStartedUsers 没有对应 UserState 而返回失败;因此“密钥解锁成功”和“用户进入运行态”是两个条件。

7. 状态回调 ​

用户状态变化通过 SystemServiceManager 广播给所有注册的 SystemService,再由 UserManagerService 自己更新时间戳、allowlist 和公共资料。

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

相关函数:Lifecycle.onUserStarting、onUserUnlocking、onUserSwitching、onUserStopping

java
@Override
public void onUserStarting(@NonNull TargetUser targetUser) {
    synchronized (mUms.mUsersLock) {
        final UserData user = mUms.getUserDataLU(
                targetUser.getUserIdentifier());
        if (user != null) {
            user.startRealtime =
                    SystemClock.elapsedRealtime();
            // ... 初始化该 user type 的 activities allowlist。
        }
    }
}

@Override
public void onUserUnlocking(@NonNull TargetUser targetUser) {
    synchronized (mUms.mUsersLock) {
        final UserData user = mUms.getUserDataLU(
                targetUser.getUserIdentifier());
        if (user != null) {
            user.unlockRealtime =
                    SystemClock.elapsedRealtime();
        }
    }
    if (targetUser.getUserIdentifier()
            == UserHandle.USER_SYSTEM
            && UserManager.isCommunalProfileEnabled()) {
        mUms.startCommunalProfile();
    }
}

@Override
public void onUserStopping(@NonNull TargetUser targetUser) {
    synchronized (mUms.mUsersLock) {
        final UserData user = mUms.getUserDataLU(
                targetUser.getUserIdentifier());
        if (user != null) {
            user.startRealtime = 0;
            user.unlockRealtime = 0;
            // ... 删除该用户的 per-user activities allowlist。
        }
    }
}

这些回调的消费者是各个 SystemService;它们不是 UserState 的替代品。UserController 的 STATE_RUNNING_LOCKED/STATE_RUNNING_UNLOCKED 决定生命周期阶段,UserManager 的 startRealtime/unlockRealtime 负责统计和用户数据侧的观察。二者更新顺序不一致时,调试输出可能短暂出现“服务已回调但 AMS 状态尚未推进”。

8. 进程记录 ​

用户启动并不会批量创建所有应用进程。应用第一次被 Activity、Service、ContentProvider 或其他组件需要时,AMS 才调用 ProcessList.startProcessLocked();此时 ProcessRecord.uid 已经包含 userId。

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

相关函数:startProcessLocked(ProcessRecord, HostingRecord, ...)

java
@GuardedBy("mService")
boolean startProcessLocked(ProcessRecord app,
        HostingRecord hostingRecord,
        int zygotePolicyFlags,
        boolean disableHiddenApiChecks,
        boolean disableTestApiChecks,
        String abiOverride) {
    if (app.isPendingStart()) {
        return true;
    }

    final int userId = UserHandle.getUserId(app.uid);
    try {
        AppGlobals.getPackageManager()
                .checkPackageStartable(
                        app.info.packageName, userId);
    } catch (RemoteException e) {
        throw e.rethrowAsRuntimeException();
    }

    int uid = app.uid;
    int[] gids = null;
    int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
    if (!app.isolated) {
        final IPackageManager pm =
                AppGlobals.getPackageManager();
        int[] permGids = pm.getPackageGids(
                app.info.packageName,
                MATCH_DIRECT_BOOT_AUTO, app.userId);
        // ... 依据 uid/package 计算 external storage mount mode。
        gids = computeGidsForProcess(
                mountExternal, uid, permGids,
                externalStorageAccess);
    }

    String seInfo = updateSeInfo(app);
    final String entryPoint =
            "android.app.ActivityThread";
    return startProcessLocked(
            hostingRecord, entryPoint, app, uid, gids,
            runtimeFlags, zygotePolicyFlags,
            mountExternal, seInfo, requiredAbi,
            instructionSet, invokeWith,
            startUptime, startElapsedTime);
}

这里出现两次 userId:第一次由 app.uid 反解出来,用于 PackageManager 的 startable 检查;第二次 app.userId 用于按用户取 GID。它们正常情况下相等,若不相等就是 ProcessRecord 状态已经损坏,不能靠进程名推断正确用户。app.isolated 还会绕过普通包的 GID 计算,隔离进程的 UID 分配另有范围。

启动失败会清理 ProcessRecord 的活动 bookkeeping,而不是留下一个“正在启动”的假进程。

java
} catch (RuntimeException e) {
    Slog.e(ActivityManagerService.TAG,
            "Failure starting process "
                    + app.processName, e);

    // 本文注:失败后强制停止对应 package/user,清掉 pending start 等状态。
    mService.forceStopPackageLocked(
            app.info.packageName,
            UserHandle.getAppId(app.uid),
            false, false, true, false, false, false,
            app.userId, "start failure");
    return false;
}

这个失败路径只处理应用进程启动,不会停止整个 user。forceStopPackageLocked() 的 appId 参数使用 UserHandle.getAppId(app.uid),而 user 参数单独传入 app.userId;这是多用户调用约定的典型例子。

9. Zygote参数 ​

ProcessList 最终把 UID、GID、mount mode、seInfo 和运行时标志传给 Zygote。Zygote 不再决定“这个包属于哪个用户”,它只执行来自 system_server 的 specialization。

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

相关函数:startViaZygote

java
private Process.ProcessStartResult startViaZygote(
        @NonNull final String processClass,
        @Nullable final String niceName,
        final int uid, final int gid,
        @Nullable final int[] gids,
        int runtimeFlags, int mountExternal,
        int targetSdkVersion,
        @Nullable String seInfo,
        @NonNull String abi,
        @Nullable String instructionSet,
        @Nullable String appDataDir,
        @Nullable String invokeWith,
        boolean startChildZygote,
        @Nullable String packageName,
        int zygotePolicyFlags,
        boolean isTopApp,
        @Nullable long[] disabledCompatChanges,
        @Nullable long[] enabledCompatChanges,
        boolean useDeliQueue,
        @Nullable Map<String, Pair<String, Long>> pkgDataInfoMap,
        @Nullable Map<String, Pair<String, Long>> allowlistedDataInfoList,
        boolean bindMountAppsData,
        boolean bindMountAppStorageDirs,
        boolean bindMountOverrideSysprops,
        long startSeq,
        @Nullable String[] extraArgs)
        throws ZygoteStartFailedEx {
    ArrayList<String> argsForZygote =
            new ArrayList<>();

    argsForZygote.add("--runtime-args");
    argsForZygote.add("--setuid=" + uid);
    argsForZygote.add("--setgid=" + gid);
    // --setgroups is encoded as a comma-separated argument.
    if (gids != null && gids.length > 0) {
        final StringBuilder sb = new StringBuilder();
        sb.append("--setgroups=");
        for (int i = 0; i < gids.length; i++) {
            if (i != 0) sb.append(',');
            sb.append(gids[i]);
        }
        argsForZygote.add(sb.toString());
    }
    argsForZygote.add("--runtime-flags="
            + runtimeFlags);
    argsForZygote.add("--target-sdk-version="
            + targetSdkVersion);
    argsForZygote.add("--seinfo=" + seInfo);
    // ... 根据 mountExternal 选择具体 mount flag,并添加 niceName/invokeWith 等参数。
    argsForZygote.add("--instruction-set=" + instructionSet);
    argsForZygote.add("--app-data-dir=" + appDataDir);
    argsForZygote.add(processClass);
    if (extraArgs != null) {
        Collections.addAll(argsForZygote,
                extraArgs);
    }

    return zygoteSendArgsAndGetResult(
            openZygoteSocketIfNeeded(abi),
            zygotePolicyFlags, argsForZygote);
}

这些参数是跨进程文本协议的消费者输入:--setuid 决定 Linux 身份,--seinfo 决定 SELinux 进程上下文,--mount-external 决定外部存储视图,--app-data-dir 提供数据目录。userId 没有作为独立 --user-id 参数发送,因为它已经编码进 uid,且 appDataDir/seInfo 又分别携带了用户侧路径和策略信息。

10. 进程特化 ​

Zygote native specialization 最终调用 setuid/setgid、设置 mount namespace、应用 SELinux 信息并关闭不应继承的文件描述符。与多用户最相关的不是 fork 本身,而是 fork 后 child 何时失去 Zygote 的 root 身份。

源码文件:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

相关函数:com_android_internal_os_Zygote_nativeForkAndSpecialize

cpp
static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
        JNIEnv* env, jclass, jint uid, jint gid,
        jintArray gids, jint runtime_flags,
        jobjectArray rlimits, jint mount_external,
        jstring se_info, jstring nice_name,
        jintArray fds_to_close, jintArray fds_to_ignore,
        jboolean is_child_zygote, jstring instruction_set,
        jstring app_data_dir, jboolean is_top_app,
        jobjectArray pkg_data_info_list,
        jobjectArray allowlisted_data_info_list,
        jboolean bind_mount_app_data_dirs) {
    // ... ForkCommon() 创建 child,并在 child 中进入 specialization。
    pid_t pid = zygote::ForkCommon(
            env, false, fds_to_close,
            fds_to_ignore, true);
    if (pid == 0) {
        SpecializeCommon(
                env, uid, gid, gids, runtime_flags,
                rlimits, 0, 0, mount_external,
                se_info, nice_name, false,
                is_child_zygote, instruction_set,
                app_data_dir, is_top_app,
                pkg_data_info_list,
                allowlisted_data_info_list,
                bind_mount_app_data_dirs);
    }
    return pid;
}

SpecializeCommon() 是真正的身份和隔离生效点,ForkCommon() 只负责创建地址空间和处理继承的 fd。对于 user 10 的普通应用,parent 仍是 Zygote,child 在 specialization 后以组合 UID 和指定 SELinux 信息运行;这也是为什么同一 Zygote 可以服务多个用户,却不会让应用共享 root 身份。

seInfoUser 在 ProcessList 的 updateSeInfo(app) 中拼接。若包信息缺少该字段,代码记录 wtf,但仍需要继续处理,否则一个包的元数据问题会被错误地描述成用户切换问题。

11. 停止用户 ​

停止入口首先拒绝不能停止的对象:system user、当前前台用户,以及与当前用户关联且策略不允许停止的 profile。通过安全检查后,父用户和 profiles 由 getUsersToStopLU() 一并计算。

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

相关函数:stopUsersLU

java
@GuardedBy("mLock")
private int stopUsersLU(final int userId,
        boolean stopProfileRegardlessOfParent,
        boolean allowDelayedLocking,
        final IStopUserCallback stopUserCallback,
        KeyEvictedCallback keyEvictedCallback) {
    if (userId == UserHandle.USER_SYSTEM) {
        return USER_OP_ERROR_IS_SYSTEM;
    }
    if (isCurrentUserLU(userId)) {
        return USER_OP_IS_CURRENT;
    }
    if (!stopProfileRegardlessOfParent) {
        final int parentId = mUserProfileGroupIds.get(
                userId, UserInfo.NO_PROFILE_GROUP_ID);
        if (parentId != UserInfo.NO_PROFILE_GROUP_ID
                && parentId != userId
                && isCurrentUserLU(parentId)) {
            return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
        }
    }

    final int[] usersToStop = getUsersToStopLU(userId);
    for (int userIdToStop : usersToStop) {
        stopSingleUserLU(
                userIdToStop, allowDelayedLocking,
                userIdToStop == userId
                        ? stopUserCallback : null,
                userIdToStop == userId
                        ? keyEvictedCallback : null);
    }
    return USER_OP_SUCCESS;
}

这个函数只在 mLock 下规划和提交停止对象,真正的 shutdown 广播和异步收尾在 stopSingleUserLU() 中完成。profile 是否随 parent 停止由参数控制;因此“停止 user 10”不等价于“停止所有 user 10 的 PID”,而是先按 profile 关系生成用户集合。

单用户收束会先进入 STATE_SHUTDOWN,通知系统服务,然后发送 ACTION_SHUTDOWN;广播完成后才进入 finishUserStopped()。

java
// UserController.stopSingleUserLU() 的收尾顺序
uss.setState(UserState.STATE_SHUTDOWN);
mInjector.getUserManagerInternal()
        .setUserState(userId, uss.state);
mInjector.getSystemServiceManager()
        .onUserStopping(userId);

final IIntentReceiver shutdownReceiver =
        new IIntentReceiver.Stub() {
    @Override
    public void performReceive(Intent intent,
            int resultCode, String data,
            Bundle extras, boolean ordered,
            boolean sticky, int sendingUser) {
        finishUserStoppedAsync.run();
    }
};

mInjector.broadcastIntent(
        new Intent(Intent.ACTION_SHUTDOWN),
        null, shutdownReceiver, 0, null, null, null,
        AppOpsManager.OP_NONE, null, false,
        MY_PID, SYSTEM_UID, Binder.getCallingUid(),
        Binder.getCallingPid(), userId);

ACTION_SHUTDOWN 是用户范围的 ordered broadcast,receiver 完成后才允许真正清掉 mStartedUsers 和回调 IStopUserCallback。如果在广播返回前直接杀进程,服务可能仍持有用户资源,下一次 start 会遇到旧状态。

12. 锁定与LRU ​

停止用户不一定马上擦除 CE key。Android 17 支持 delayed data locking:后台用户停止后可以暂时保留解锁状态,直到超过 mMaxRunningUsers,再从 LRU 尾部选择用户锁定。

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

相关函数:finishUserStopped

java
@VisibleForTesting
void finishUserStopped(UserState uss,
        boolean allowDelayedLocking) {
    final int userId = uss.mHandle.getIdentifier();
    final UserInfo userInfo = getUserInfo(userId);
    final boolean stopped;
    boolean lockUser = true;
    int userIdToLock = userId;
    final ArrayList<IStopUserCallback> stopCallbacks;
    final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
    synchronized (mLock) {
        // 本文注:在同一把锁下快照 callback,再校验 UserState 是否仍有效。
        stopCallbacks = new ArrayList<>(uss.mStopCallbacks);
        keyEvictedCallbacks = new ArrayList<>(uss.mKeyEvictedCallbacks);
        if (mStartedUsers.get(userId) != uss
                || uss.state != UserState.STATE_SHUTDOWN) {
            stopped = false;
        } else {
            mStartedUsers.remove(userId);
            mUserLru.remove(Integer.valueOf(userId));
            updateStartedUserArrayLU();
            stopped = true;
            userIdToLock = updateUserToLockLU(
                    userInfo, allowDelayedLocking);
            lockUser = userIdToLock != UserHandle.USER_NULL;
        }
    }
    if (!stopped) {
        return;
    }

    mInjector.getUserManagerInternal()
            .removeUserState(userId);
    mInjector.activityManagerOnUserStopped(userId);

    // 本文注:用户状态移除后,才清理该用户的 package 进程。
    stopPackagesOfStoppedUser(userId, "finish user");

    if (lockUser) {
        // CE key 的驱逐在 FgThread 串行执行,避免与解锁路径并发。
        dispatchUserLocking(userIdToLock,
                keyEvictedCallbacks);
    }
    for (IStopUserCallback callback : stopCallbacks) {
        try {
            callback.userStopped(userId);
        } catch (RemoteException ignored) {
        }
    }
    mInjector.systemServiceManagerOnUserStopped(userId);
    resumePendingUserStarts(userId);
}

mStartedUsers.get(userId) != uss 是 stale-state 保护:旧的异步 shutdown 回调不能删除后来重新创建的同一 userId 状态。真正的用户进程清理是 stopPackagesOfStoppedUser();CE key 驱逐则由 dispatchUserLocking() 发到 FgThread,可能因 delayed-locking 改为锁定另一个 LRU 用户。它们都在状态删除后发生,但不是同一个同步操作。

运行用户的 LRU 只把“当前用户”和 system user 视为不可淘汰,其他用户可能被停止以满足软上限。

java
@GuardedBy("mLock")
private void stopExcessRunningUsersLU(
        int maxRunningUsers,
        ArraySet<Integer> exemptedUsers,
        ArraySet<Integer> avoidUsers) {
    List<Integer> currentlyRunningLru =
            getRunningUsersLU();
    Iterator<Integer> iterator =
            currentlyRunningLru.iterator();
    while (currentlyRunningLru.size() > maxRunningUsers
            && iterator.hasNext()) {
        final Integer userId = iterator.next();
        if (userId == UserHandle.USER_SYSTEM
                || userId == mCurrentUserId
                || exemptedUsers.contains(userId)) {
            continue;
        }
        if (avoidUsers.contains(userId)) {
            // 本文注:avoid 只是延后候选,不是永久豁免。
            continue;
        }
        if (stopUsersLU(userId,
                false, true, null, null)
                == USER_OP_SUCCESS) {
            iterator.remove();
        }
    }
}

这是一个软上限:如果所有候选都被 system/current/exempt/avoid 排除,函数会保留超额运行用户并记录日志。软上限和“每次切换都杀掉旧用户”不同,后者会破坏后台用户的快速恢复和 delayed locking 设计。

13. 全链路关系 ​

状态图只描述 UserState,不是 Linux 进程状态机。一个用户处于 STATE_RUNNING_LOCKED 时可以没有任何第三方应用进程;一个用户停止后,某些进程可能在 shutdown 收尾阶段仍存在。进程 PID 的创建和死亡由 ProcessList/AMS 另行维护。

状态进入者主要消费者关键边界
STATE_BOOTINGstartUserInternalUserManager、SystemServiceManagerDE 准备和 USER_STARTED
STATE_RUNNING_LOCKEDfinishUserBootSystemService、广播用户运行但 CE 未解锁
STATE_RUNNING_UNLOCKEDfinishUserUnlocking应用数据、ProfileCE key 已可用
STATE_STOPPINGstop 计划shutdown 流程仍可能有异步回调
STATE_SHUTDOWNshutdown 广播前后finishUserStopped可排队 pending start

14. 诊断路径 ​

只读诊断要同时观察用户状态、进程 UID、数据准备和启动日志:

bash
# 输入:ActivityManager 用户状态。输出:当前用户、started/running 状态和 UserState。
adb shell dumpsys activity users

# 输入:UserManager 持久化与运行时状态。输出:userId、flags、profile parent、state。
adb shell dumpsys user

# 输入:目标 UID。输出:该 UID 对应的 userId/appId 拆分和进程列表。
adb shell cmd package list packages --uid 1010001
adb shell ps -A -o PID,UID,NAME | grep '1010001\|u10_'

# 输入:用户生命周期日志。输出:start/stop/unlock 的 EventLog 和失败原因。
adb logcat -b system -v threadtime -d \
  | rg 'UserController|UC_START_USER|UC_FINISH_USER_BOOT|stopSingleUser|ACTION_SHUTDOWN|reconcileAppsData'

# 输入:Zygote 启动日志。输出:进程 specialization 失败、SELinux 或 UID 错误。
adb logcat -b system -b main -v threadtime -d \
  | rg 'Failure starting process|Zygote|SELinux tag not defined|setuid|specialize'

命令的判断顺序应当固定:先确认 user 10 是否在 mStartedUsers,再确认 ProcessRecord 的 UID 是否落在 10 万范围,最后看 DE/CE 准备和 Zygote specialization。若只看到 u10_a123 就断定用户已解锁,可能把 locked-running 用户误判成 unlocked。

15. 测试边界 ​

UserController 的完整生命周期依赖 system_server、广播和存储服务,单元测试通常覆盖状态决策而不是实际切换画面。阅读测试时先看输入和锁,再看断言的状态。

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

相关测试:testStartUser_background、testStartUser_foreground_notReady

java
@Test
public void testStartUser_background() {
    mUserController.setInitialConfig(
            /* userSwitchUiEnabled= */ true,
            /* maxRunningUsers= */ 3,
            /* delayUserDataLocking= */ false,
            /* backgroundUserConsideredDispensableTimeSecs= */ -1,
            /* skipKeyguardWhenSwitchingToUnlockedUsers= */ false,
            /* hideUserSwitchingUiDuringSetup= */ false);

    boolean started = mUserController.startUser(
            TEST_USER_ID, USER_START_MODE_BACKGROUND);

    // 断言:后台启动不打开用户切换 UI,但会分配显示器并排队用户消息。
    assertWithMessage("background start")
            .that(started).isTrue();
    verify(mInjector, never()).showUserSwitchingDialog(
            any(), any(), anyString(), anyString(), anyBoolean(), any());
    verifyUserAssignedToDisplay(
            TEST_USER_ID, Display.DEFAULT_DISPLAY);
    startBackgroundUserAssertions();
}

testStartUser_background 的 arrange 是设置 mMaxRunningUsers=3、关闭 delayed locking 并以 background mode 启动测试用户;assert 同时覆盖返回值、没有切换 UI、显示器分配和 Handler 消息。它证明的是 UserController 的编排,不证明应用进程一定已经创建。

前置条件失败由另一条测试覆盖:

java
@Test
public void testStartUser_foreground_notReady() {
    var e = assertThrows(
            IllegalStateException.class,
            () -> mNotReadyUserController.startUser(
                    TEST_USER_ID,
                    USER_START_MODE_FOREGROUND));

    // 断言:mReady=false 时不能跳过用户生命周期门槛。
    assertThat(e).hasMessageThat().isEqualTo(
            String.format(Locale.ENGLISH,
                    UserController
                            .EXCEPTION_TEMPLATE_CANNOT_START_USER_WHEN_NOT_READY,
                    TEST_USER_ID));
}

这个测试的输入是一个 mReady=false 的 UserController,关键断言是异常类型和完整错误消息;它说明“start 返回 false”和“前置条件直接抛异常”是两类不同失败。

延迟锁定的设备行为由多用户测试的 finishUserStopped() 分支反向检查。

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

相关测试:testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode

java
// arrange: maxRunningUsers=3, delayUserDataLocking=true
mUserController.setInitialConfig(
        true, 3, true, -1, false, false);

// 模拟用户已经停止,但允许延迟锁定。
UserState uss = mUserStates.get(TEST_USER_ID);
uss.setState(UserState.STATE_SHUTDOWN);
mUserController.finishUserStopped(
        uss, /* allowDelayedLocking= */ true);
waitForHandlerToComplete(
        FgThread.getHandler(), HANDLER_WAIT_TIME_MS);

// 断言:第一个停止的 user 可以保留解锁,不立即调用 lockUser。
verify(mInjector.mLockSettingsInternalMock,
        times(0)).lockUser(anyInt());

这个用例的 arrange/action/assert 证明 delayed locking 会把锁定动作延后到 LRU 超过上限;它不证明所有设备都开启该策略。需要在真机上观察 CE key、vold 和实际 UID 时,还应结合上一节的 dumpsys user、dumpsys activity users 和进程列表。

bash
# 输入:AOSP 源码树。输出:UserController 生命周期测试、start/stop 断言和 fake injector。
rg -n 'UserController|startUser|stopUser|STATE_RUNNING_LOCKED|STATE_SHUTDOWN|mStartedUsers' \
  frameworks/base/services/tests \
  frameworks/base/services/core/java/com/android/server/am/UserController.java

# 输入:AOSP 源码树。输出:用户数据准备与 CE 解锁的测试替身和断言。
rg -n 'onBeforeStartUser|onBeforeUnlockUser|prepareUserData|markCeStoragePrepared|reconcileAppsData' \
  frameworks/base/services/tests \
  frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java

这两个搜索命令的输入是固定版本源码树,输出是可继续阅读的测试与实现位置;它们不把“找到字符串”冒充为运行验证。真正的设备实验还要记录用户是否有锁屏凭据、是否为 profile、是否启用了 delayed locking,以及进程是否是 isolated process。

16. 收束 ​

一条完整的后台启动路径可以这样复述:AMS Binder 入口把 userId 和 start mode 交给 UserController;UserController 在 mLock 下创建或复用 UserState,更新 mStartedUsers 和 LRU;UserManagerService 准备 DE 数据、迁移 app data 并应用限制;SystemServiceManager 分发 onUserStarting;用户进入 RUNNING_LOCKED 后,具体组件需要应用时,ProcessList 从 ProcessRecord.uid 反解 userId、取该用户的 GID 和 SELinux 信息,最后把组合 UID、mount mode、seInfo 和 data dir 送入 Zygote;解锁后再准备 CE 并推进 RUNNING_UNLOCKED。

停止路径则反向执行:UserController 先拒绝 system/current/相关 profile 的非法停止,按 profile 关系计算集合,进入 STATE_SHUTDOWN,通知系统服务并等待 ACTION_SHUTDOWN 完成,再由 finishUserStopped() 删除用户状态、回调消费者,并按 delayed-locking 策略决定何时清理 CE key。进程的创建、死亡和用户状态不是一条状态机;只有把它们按 owner 和生效时机分开,才能解释多用户下的 UID、数据和进程行为。