Android7 多用户源码解析(二)核心数据结构深度剖析

系列目录 :第一篇:功能演进与系统架构总览 | 第二篇:核心数据结构深度剖析 | 第三篇:用户创建与删除流程全链路分析 | 第四篇:用户切换机制源码剖析 | 第五篇:应用安装与管理隔离机制 | 第六篇:数据隔离与存储机制详解 | 第七篇:系统服务的多用户感知处理 | [第八篇:广播分发与 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 的防抖是同一套路。


七、本篇总结

本篇厘清了四个数据结构的真实面貌:

  1. UserInfo :flags 位掩码区分四类用户;id 从 10 起(上限受 UID int 溢出约束,约 21474 个),serialNumber 用于外部身份;profileGroupId/restrictedProfileParentId 支撑 Work Profile 与受限配置;
  2. UserHandle :负值哨兵延迟解析,PER_USER_RANGE = 100000 的 UID 映射是隔离基石;
  3. UserManager 客户端 :薄 facade,多用户开关由 fw.max_users 与 config_enableMultiUserUI 双控;
  4. 持久化 :/data/system/users/<id>.xml 用 AtomicFile 写,系统限制与 DPC 限制分标签存。

下一篇进入用户生命周期,追踪 createUser() 从客户端调用到用户目录就绪的完整链路。

相关推荐
沫璃染墨1 小时前
《从零入门Linux系统篇(五十一):线程篇·四——pthread线程库详解:从线程创建到终止与分离》
linux·运维·服务器·开发语言·c++·系统架构·线程
带金箍的至尊宝1 小时前
系统架构设计师笔记 03:CPU 结构、Cache 与总线怎么考
笔记·系统架构
新鲜势力呀3 小时前
PHP 实战:用户登录频繁失败怎么办?从安全防护到登录系统架构优化完整方案
安全·系统架构·php
励志不掉头发的内向程序员6 小时前
【从零写一个CAD 03】三个 double 值得单独一个类吗:把视图变换抽成 View
开发语言·c++·qt·学习·系统架构
Travis_del6 小时前
系统架构设计师考试大纲:考试说明
系统架构·软考·系统架构设计师
AFinalStone14 小时前
Android7 多用户源码解析(六)数据隔离与存储机制详解
系统架构·aosp·多用户
AFinalStone18 小时前
Android7 多用户源码解析(一)功能演进与系统架构总览
系统架构·aosp·多用户·usermanaher
Jmyd012318 小时前
智慧文博数字化系统架构设计:从三维采集到平台应用的全链路方案
系统架构
一切皆是因缘际会1 天前
掌控信息论:同源星际通信零延迟架构
人工智能·深度学习·ai·系统架构·信息与通信·星际通信·同源通信