多用户进程
本文面向已经读过 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.mStartedUsers | ActivityManager | UserState、用户是否允许执行代码 | 加入 map 后开始启动流程 |
UserManagerService.mUserStates | UserManager | 对其他服务公开的用户状态 | setUserState() 更新后 |
ProcessRecord | ProcessList/AMS | uid、userId、PID、startSeq | 创建或重启进程时 |
| CE/DE 用户存储 | vold/UserDataPreparer | DE 可用、CE 是否解锁 | onBeforeStartUser / onBeforeUnlockUser |
mUserLru | UserController | 运行用户淘汰顺序 | 每次 start 请求后移动到末尾 |
2. UID编码
源码文件:frameworks/base/core/java/android/os/UserHandle.java
相关常量与函数:PER_USER_RANGE、getUserId、getUid、getAppId
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
@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
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 的插入是状态提交点之一。
// 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
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 已可用。
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
@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, ...)
@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,而不是留下一个“正在启动”的假进程。
} 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
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
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
@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()。
// 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
@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 视为不可淘汰,其他用户可能被停止以满足软上限。
@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_BOOTING | startUserInternal | UserManager、SystemServiceManager | DE 准备和 USER_STARTED |
STATE_RUNNING_LOCKED | finishUserBoot | SystemService、广播 | 用户运行但 CE 未解锁 |
STATE_RUNNING_UNLOCKED | finishUserUnlocking | 应用数据、Profile | CE key 已可用 |
STATE_STOPPING | stop 计划 | shutdown 流程 | 仍可能有异步回调 |
STATE_SHUTDOWN | shutdown 广播前后 | finishUserStopped | 可排队 pending start |
14. 诊断路径
只读诊断要同时观察用户状态、进程 UID、数据准备和启动日志:
# 输入: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
@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 的编排,不证明应用进程一定已经创建。
前置条件失败由另一条测试覆盖:
@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
// 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 和进程列表。
# 输入: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、数据和进程行为。
