系列目录 :第一篇:功能演进与系统架构总览 | 第二篇:核心数据结构深度剖析 | 第三篇:用户创建与删除流程全链路分析 | 第四篇:用户切换机制源码剖析 | 第五篇:应用安装与管理隔离机制 | 第六篇:数据隔离与存储机制详解 | 第七篇:系统服务的多用户感知处理 | [第八篇:广播分发与 Intent 路由的用户隔离](#第八篇:广播分发与 Intent 路由的用户隔离) | 第九篇:权限系统与多用户的交互机制 | [第十篇:Work Profile 企业模式与开发实战指南](#第十篇:Work Profile 企业模式与开发实战指南)
一、为什么要先讲数据结构
读多用户源码时,这三个问题会反复出现:
UserInfo的id和serialNumber到底有什么区别?为什么要有两个标识?- 用户配置持久化在
/data/system/users/下的 XML 里,哪些字段会被写进去、哪些只在内存中? - App 进程里
UserManager的每个调用,到底跨了哪条 Binder 通道、落在UserManagerService的哪个方法上?
本篇把 UserInfo、UserHandle、UserManager 客户端与 IUserManager AIDL 接口、/data/system/users/<id>.xml 持久化格式逐一对照源码拆解,为第三、四篇的生命周期分析打好数据基础。
二、UserInfo --- 用户元信息载体
2.1 字段与标志位
UserInfo 描述一个用户的全部元信息,用户类型由 flags 位掩码表达。
源码路径 :frameworks/base/core/java/android/content/pm/UserInfo.java
java
public class UserInfo implements Parcelable {
public static final int FLAG_MASK_USER_TYPE = 0x0000FFFF;
public static final int FLAG_PRIMARY = 0x00000001; // 主用户
public static final int FLAG_ADMIN = 0x00000002; // 管理员
public static final int FLAG_GUEST = 0x00000004; // 访客
public static final int FLAG_RESTRICTED = 0x00000008; // 受限
public static final int FLAG_INITIALIZED = 0x00000010;
public static final int FLAG_MANAGED_PROFILE = 0x00000020; // 工作配置
public static final int FLAG_DISABLED = 0x00000040;
public static final int FLAG_QUIET_MODE = 0x00000080;
public static final int FLAG_EPHEMERAL = 0x00000100; // 临时(数据不落盘)
public static final int FLAG_DEMO = 0x00000200; // 演示用户
public int id; // 用户 ID,主用户为 0
public int serialNumber; // 序列号,与 id 不同(见 2.3)
public String name;
public String iconPath;
public int flags;
public long creationTime;
public long lastLoggedInTime;
public String lastLoggedInFingerprint;
public int profileGroupId; // 配置组 ID
public int restrictedProfileParentId; // 受限配置的宿主用户 ID
public boolean partial; // 仅部分创建(开机恢复中间态)
public boolean guestToRemove;
public boolean isGuest() {
return (flags & FLAG_GUEST) == FLAG_GUEST;
}
}
关键设计 :
id是持久化的用户身份,profileGroupId/restrictedProfileParentId把 Work Profile、受限配置"挂"在宿主用户上------这是第十篇 Work Profile 的数据基础。partial标记开机恢复时的半创建用户,防止半成品用户被当作完整用户处理。
2.2 ID 分配规则
UserManagerService 中用户 ID 从 10 开始,最大值由 UID 上限反推:
源码路径 :frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
java
public class UserManagerService extends SystemService {
private static final int MIN_USER_ID = 10;
// We need to keep process uid within Integer.MAX_VALUE.
private static final int MAX_USER_ID = Integer.MAX_VALUE / UserHandle.PER_USER_RANGE;
// ...
private int getNextAvailableId() {
synchronized (mUsersLock) {
int i = MIN_USER_ID;
while (i < MAX_USER_ID) {
if (mUsers.indexOfKey(i) < 0 && !mRemovingUserIds.get(i)) {
return i;
}
i++;
}
return UserHandle.USER_NULL;
}
}
}
关键设计 :
MAX_USER_ID = Integer.MAX_VALUE / 100000(约 21474)------上限不是拍脑袋定的 999,而是受UID = userId * 100000 + appId不能溢出 int 的硬约束。mRemovingUserIds把"正在删除中"的 ID 也排除掉,避免删除流程未结束时新用户复用该 ID 导致数据混写。
2.3 serialNumber 与 id 的区别
两者都是 int,但职责不同:
id:系统内部身份,从 10 递增,删除后不立即复用;serialNumber:外部(跨设备备份恢复等场景)使用的序号,创建时取mNextSerialNumber++,开机时从list文件恢复并校准。
mNextSerialNumber 的初始化逻辑(开机加载 list 文件时):
java
// UserManagerService.java 开机加载阶段
mNextSerialNumber = -1;
// ... 从 list 文件读出 lastSerialNumber ...
mNextSerialNumber = Integer.parseInt(lastSerialNumber);
// 校准:serialNumber 必须大于任何已存在用户的 id
if (mNextSerialNumber < 0 || mNextSerialNumber <= userData.info.id) {
mNextSerialNumber = userData.info.id + 1;
}
关键设计 :serialNumber 持久化在用户列表文件中并带校准逻辑,保证"备份恢复用户"时序号不会冲突。App 层拿到
UserInfo.serialNumber后,跨设备比较用户身份时用它而不是id(id在不同设备上可能不同)。
2.4 Parcel 序列化
UserInfo 实现了 Parcelable,支持跨进程传递。writeToParcel 按声明顺序写入 id、serialNumber、name、iconPath、flags、creationTime、lastLoggedInTime、lastLoggedInFingerprint、profileGroupId、restrictedProfileParentId、partial 等字段------CREATOR 按同样顺序读回。
关键设计 :
UserInfo的序列化顺序与类字段顺序一一对应,修改字段时必须同步改writeToParcel/构造函数,否则跨进程读写错乱------这是改 framework 时最容易踩的坑之一。
三、UserHandle --- 用户标识与 UID 映射
3.1 常量体系
源码路径 :frameworks/base/core/java/android/os/UserHandle.java
java
public final class UserHandle implements Parcelable {
/** @hide Range of UIDs allocated to a user. */
public static final int PER_USER_RANGE = 100000;
public static final @UserIdInt int USER_ALL = -1;
public static final @UserIdInt int USER_CURRENT = -2;
public static final @UserIdInt int USER_CURRENT_OR_SELF = -3;
public static final @UserIdInt int USER_NULL = -10000;
public static final @UserIdInt int USER_OWNER = 0;
public static final @UserIdInt int USER_SYSTEM = 0;
public static final boolean MU_ENABLED = true;
public static final UserHandle ALL = new UserHandle(USER_ALL);
public static final UserHandle CURRENT = new UserHandle(USER_CURRENT);
public static final UserHandle CURRENT_OR_SELF = new UserHandle(USER_CURRENT_OR_SELF);
public static final UserHandle OWNER = new UserHandle(USER_OWNER);
// ...
public boolean isOwner() {
return this.equals(OWNER);
}
public static boolean isSameUser(int uid1, int uid2) {
return getUserId(uid1) == getUserId(uid2);
}
public static boolean isSameApp(int uid1, int uid2) {
return getAppId(uid1) == getAppId(uid2);
}
}
特殊值速查:
| 常量 | 值 | 语义 |
|---|---|---|
USER_SYSTEM / USER_OWNER |
0 | 主用户 |
USER_ALL |
-1 | 所有用户(广播、安装等 API 参数) |
USER_CURRENT |
-2 | 当前前台用户(运行时解析) |
USER_CURRENT_OR_SELF |
-3 | 当前或调用者(权限检查) |
USER_NULL |
-10000 | 无效用户 / "无父用户"占位 |
关键设计 :负值哨兵(-1/-2/-3/-10000)在调用链中被延迟解析------
USER_CURRENT传到底层时才通过ActivityManager.getCurrentUser()解析成真实 ID。这让 API 可以在不知道"现在是谁"的情况下表达"当前用户"。
3.2 UID 映射
java
// UserHandle.java
public static @UserIdInt int getUserId(int uid) {
if (MU_ENABLED) {
return uid / PER_USER_RANGE;
} else {
return UserHandle.USER_SYSTEM;
}
}
public static @AppIdInt int getAppId(int uid) {
return uid % PER_USER_RANGE;
}
public static int getUid(@UserIdInt int userId, @AppIdInt int appId) {
if (MU_ENABLED) {
return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
} else {
return appId;
}
}
关键设计 :
getUid里的appId % PER_USER_RANGE是防御性取模------appId本身就在 0~99999 区间,取模保证传入异常值时不产生跨用户区间的 UID。
四、UserManager 客户端 API
UserManager 是 App 侧的 facade,每个方法都是"取 mService(IUserManager Binder 代理)→ 调用 → 转 RemoteException"的薄封装。
源码路径 :frameworks/base/core/java/android/os/UserManager.java
java
public class UserManager {
private final Context mContext;
private final IUserManager mService; // Binder 代理
public UserManager(Context context, IUserManager service) {
mService = service;
mContext = context;
}
public static boolean supportsMultipleUsers() {
return getMaxSupportedUsers() > 1
&& SystemProperties.getBoolean("fw.show_multiuserui",
Resources.getSystem().getBoolean(R.bool.config_enableMultiUserUI));
}
public static int getMaxSupportedUsers() {
// Don't allow multiple users on certain builds
if (android.os.Build.ID.startsWith("JVP")) return 1;
// Svelte devices don't get multi-user.
if (ActivityManager.isLowRamDeviceStatic()) return 1;
return SystemProperties.getInt("fw.max_users",
Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers));
}
public UserInfo createUser(String name, int flags) {
try {
return mService.createUser(name, flags);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
public boolean removeUser(@UserIdInt int userHandle) {
try {
return mService.removeUser(userHandle);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
}
关键设计 :多用户是否可用由两个开关 共同决定:
getMaxSupportedUsers() > 1(系统属性fw.max_users或资源config_multiuserMaximumUsers)与config_enableMultiUserUI。低内存设备(svelte)强制单用户。OEM 改这两个资源/属性即可控制整机行为------这解释了为什么同样是 AOSP 7,有的设备进不去多用户设置。
4.1 用户限制(UserRestriction)
UserRestrictions 是按用户的布尔键值集合,是 DPC(设备策略控制)限制应用能力的通道:
java
// UserManager.java
public boolean hasUserRestriction(String restrictionKey, UserHandle userHandle) {
try {
Bundle restrictions = mService.getUserRestrictions(userHandle.getIdentifier());
return restrictions != null && restrictions.getBoolean(restrictionKey, false);
} catch (RemoteException re) {
return false;
}
}
DISALLOW_ADD_USER、DISALLOW_CONFIG_WIFI、DISALLOW_INSTALL_UNKNOWN_SOURCES 等几十个 key 都走这套机制。
关键设计 :限制按用户维度存(
UserManagerService中每个用户一个Bundle),Work Profile 可独立于宿主用户设置限制------企业设备"工作配置里禁止装应用、个人配置允许"就靠这个。
五、IUserManager AIDL 接口
IUserManager 定义在 frameworks/base/core/java/android/os/IUserManager.aidl,AOSP 7 的实际接口:
aidl
interface IUserManager {
/*
* DO NOT MOVE - UserManager.h depends on the ordering of this function.
*/
int getCredentialOwnerProfile(int userHandle);
UserInfo createUser(in String name, int flags);
UserInfo createProfileForUser(in String name, int flags, int userHandle);
UserInfo createRestrictedProfile(String name, int parentUserHandle);
boolean removeUser(int userHandle);
void setUserName(int userHandle, String name);
void setUserIcon(int userHandle, in Bitmap icon);
ParcelFileDescriptor getUserIcon(int userHandle);
UserInfo getPrimaryUser();
List<UserInfo> getUsers(boolean excludeDying);
List<UserInfo> getProfiles(int userHandle, boolean enabledOnly);
int[] getProfileIds(int userId, boolean enabledOnly);
UserInfo getUserInfo(int userHandle);
int getUserSerialNumber(int userHandle);
int getUserHandle(int userSerialNumber);
Bundle getUserRestrictions(int userHandle);
boolean hasUserRestriction(in String restrictionKey, int userHandle);
void setUserRestriction(String key, boolean value, int userHandle);
boolean isUserUnlocked(int userId);
boolean isUserRunning(int userId);
// ... 还有 setQuietModeEnabled、setSeedAccountData 等
}
关键设计 :AIDL 头部注释明确警告
getCredentialOwnerProfile的顺序不能动------native 侧UserManager.h按序号依赖。AIDL 方法顺序就是跨语言 ABI,改动即破坏兼容。另外注意:用户切换不在IUserManager上 ------switchUser走IActivityManager,这是第四篇的主题。
六、用户配置持久化
6.1 XML 格式
每个用户的配置写在 /data/system/users/<userId>.xml。真实写入代码:
源码路径 :frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
java
public class UserManagerService extends SystemService {
// ...
private void writeUserLP(UserData userData) {
// ...
final UserInfo userInfo = userData.info;
serializer.startTag(null, TAG_USER);
serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
serializer.attribute(null, ATTR_SERIAL_NO, Integer.toString(userInfo.serialNumber));
serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime));
serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME,
Long.toString(userInfo.lastLoggedInTime));
if (userInfo.lastLoggedInFingerprint != null) {
serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
userInfo.lastLoggedInFingerprint);
}
if (userInfo.iconPath != null) {
serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath);
}
if (userInfo.partial) {
serializer.attribute(null, ATTR_PARTIAL, "true");
}
if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
serializer.attribute(null, ATTR_PROFILE_GROUP_ID,
Integer.toString(userInfo.profileGroupId));
}
// ...
if (userInfo.name != null) {
serializer.startTag(null, TAG_NAME);
serializer.text(userInfo.name);
serializer.endTag(null, TAG_NAME);
}
synchronized (mRestrictionsLock) {
UserRestrictionsUtils.writeRestrictions(serializer,
mBaseUserRestrictions.get(userInfo.id), TAG_RESTRICTIONS);
UserRestrictionsUtils.writeRestrictions(serializer,
mDevicePolicyLocalUserRestrictions.get(userInfo.id),
TAG_DEVICE_POLICY_RESTRICTIONS);
}
// ...
serializer.endTag(null, TAG_USER);
// ...
}
}
写出的文件大致长这样:
xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<user id="10" serialNumber="10" flags="16" creationTime="0" lastLoggedInTime="0"
profileGroupId="0" version="6">
<name>User 2</name>
<restrictions version="1">
<entry key="no_install_apps" value="true" />
</restrictions>
<devicePolicyRestrictions version="1" />
</user>
关键设计 :限制分两个标签写------
<restrictions>是系统基线限制,<devicePolicyRestrictions>是 DPC 下发的限制,读取时合并判断。这样 DPC 卸载后系统限制不丢,反之亦然。version属性(AOSP 7 为 6)用于格式升级迁移。
6.2 调试入口
bash
# 列出所有用户
adb shell pm list users
# 用户服务状态
adb shell dumpsys user
# 用户配置 XML(root)
adb shell su -c "cat /data/system/users/10.xml"
关键设计 :
writeUserLP通过WRITE_USER_MSG延迟消息合并写盘(sendMessageDelayed),高频修改不会触发高频 IO------和Settings的防抖是同一套路。
七、本篇总结
本篇厘清了四个数据结构的真实面貌:
UserInfo:flags位掩码区分四类用户;id从 10 起(上限受 UID int 溢出约束,约 21474 个),serialNumber用于外部身份;profileGroupId/restrictedProfileParentId支撑 Work Profile 与受限配置;UserHandle:负值哨兵延迟解析,PER_USER_RANGE = 100000的 UID 映射是隔离基石;UserManager客户端 :薄 facade,多用户开关由fw.max_users与config_enableMultiUserUI双控;- 持久化 :
/data/system/users/<id>.xml用AtomicFile写,系统限制与 DPC 限制分标签存。
下一篇进入用户生命周期,追踪 createUser() 从客户端调用到用户目录就绪的完整链路。