系列目录 :第一篇:功能演进与系统架构总览 | 第二篇:核心数据结构深度剖析 | 第三篇:用户创建与删除流程全链路分析 | 第四篇:用户切换机制源码剖析 | 第五篇:应用安装与管理隔离机制 | 第六篇:数据隔离与存储机制详解 | 第七篇:系统服务的多用户感知处理 | [第八篇:广播分发与 Intent 路由的用户隔离](#第八篇:广播分发与 Intent 路由的用户隔离) | 第九篇:权限系统与多用户的交互机制 | [第十篇:Work Profile 企业模式与开发实战指南](#第十篇:Work Profile 企业模式与开发实战指南)
Work Profile(工作配置)是多用户体系里最"特殊"的用户类型:它和主用户共享同一个启动器、同一个锁屏、同一个前台会话 ,但在数据、应用、权限、策略上又是完全独立的第二个用户空间。企业应用(MAM)用它隔离工作数据,setUserRestriction 用它禁用相机、截屏、安装来源等能力。
本篇回答三个问题:
- Work Profile 在
UserManagerService里如何创建?为什么每个用户最多只能有 1 个? - Profile Owner 是如何被设置并持久化的?管控策略落在哪一层?
- 系统开发者做多用户定制时的真实入口在哪,实战中如何调试?
需要说明:Work Profile 的 UI 表现(应用图标徽章、通知徽章)由 Launcher 和 SystemUI 实现,不在 framework 服务侧;原稿中 NotificationData.addWorkBadge、setWorkProfileSuspended 等 API 在 AOSP 7 源码中不存在,以下内容全部基于真实源码。
一、Work Profile 与 Secondary User 的本质区别
先明确三类用户的差异,再进入源码:
| 特性 | 主用户(Primary) | 次要用户(Secondary) | Work Profile |
|---|---|---|---|
| 创建 API | 设备初始化 | UserManager.createUser(name, flags) |
UserManager.createProfileForUser(name, flags, parentUserId) |
| 标志位 | FLAG_PRIMARY (0x1) |
FLAG_INITIALIZED (0x10) |
FLAG_MANAGED_PROFILE (0x20) |
| 父用户 | 无 | 无 | 有(profileGroupId 关联) |
| 启动器/锁屏 | 独立 | 独立(需切换用户) | 与父用户共享 |
| 数量限制 | 1 | fw.max_users 限制 |
每父用户 1(硬编码) |
| 管控方 | 用户自己 | 主用户 | Profile Owner(设备管理组件) |
Work Profile 的核心特征在 UserInfo.flags 里只有一个比特位:
源码路径 :frameworks/base/core/java/android/content/pm/UserInfo.java
java
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;
关键设计 :
FLAG_MANAGED_PROFILE(0x20)是 Work Profile 的唯一身份标识。UserInfo还提供isManagedProfile()、isProfileOf(parent)、canHaveProfile()等便捷方法。注意FLAG_INITIALIZED是 0x10 而非 0x4,FLAG_RESTRICTED是 0x8------原稿附录中的标志位值全部有误,以源码为准。
"共享前台"这一点体现在:Work Profile 不参与 switchUser 流程(它没有独立的启动器和锁屏),而是作为父用户的"附属 profile"存在。UserInfo.profileGroupId 把 profile 与父用户关联起来(见下文创建流程)。
二、Work Profile 的创建流程
2.1 入口:createProfileForUser
源码路径 :frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
java
@Override
public UserInfo createProfileForUser(String name, int flags, int userId) {
checkManageOrCreateUsersPermission(flags);
return createUserInternal(name, flags, userId);
}
@Override
public UserInfo createUser(String name, int flags) {
checkManageOrCreateUsersPermission(flags);
return createUserInternal(name, flags, UserHandle.USER_NULL);
}
关键设计 :
createUser和createProfileForUser共用createUserInternal,区别只在第三个参数parentId------createUser传USER_NULL(无父用户),createProfileForUser传父用户的 userId。权限校验走checkManageOrCreateUsersPermission(flags),需要MANAGE_USERS(signature|privileged)或CREATE_USERS(signature)。
2.2 核心:createUserInternalUnchecked
java
// 源码路径同上(UserManagerService.java)
private UserInfo createUserInternalUnchecked(String name, int flags, int parentId) {
if (ActivityManager.isLowRamDeviceStatic()) {
return null;
}
final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
final boolean isDemo = (flags & UserInfo.FLAG_DEMO) != 0;
final long ident = Binder.clearCallingIdentity();
UserInfo userInfo;
UserData userData;
final int userId;
try {
synchronized (mPackagesLock) {
UserData parent = null;
if (parentId != UserHandle.USER_NULL) {
synchronized (mUsersLock) {
parent = getUserDataLU(parentId);
}
if (parent == null) return null;
}
if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
return null;
}
if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
return null;
}
if (isGuest && findCurrentGuestUser() != null) {
return null;
}
// ...restricted profile 的父用户校验...
userId = getNextAvailableId();
Environment.getUserSystemDirectory(userId).mkdirs();
boolean ephemeralGuests = Resources.getSystem()
.getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
synchronized (mUsersLock) {
if ((isGuest && ephemeralGuests) || mForceEphemeralUsers
|| (parent != null && parent.info.isEphemeral())) {
flags |= UserInfo.FLAG_EPHEMERAL;
}
userInfo = new UserInfo(userId, name, null, flags);
userInfo.serialNumber = mNextSerialNumber++;
long now = System.currentTimeMillis();
userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
userInfo.partial = true;
userInfo.lastLoggedInFingerprint = Build.FINGERPRINT;
userData = new UserData();
userData.info = userInfo;
mUsers.put(userId, userData);
}
writeUserLP(userData);
writeUserListLP();
if (parent != null) {
if (isManagedProfile) {
if (parent.info.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
parent.info.profileGroupId = parent.info.id;
writeUserLP(parent);
}
userInfo.profileGroupId = parent.info.profileGroupId;
} else if (isRestricted) {
if (parent.info.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
parent.info.restrictedProfileParentId = parent.info.id;
writeUserLP(parent);
}
userInfo.restrictedProfileParentId = parent.info.restrictedProfileParentId;
}
}
}
final StorageManager storage = mContext.getSystemService(StorageManager.class);
storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
mPm.prepareUserData(userId, userInfo.serialNumber,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
mPm.createNewUser(userId);
userInfo.partial = false;
synchronized (mPackagesLock) {
writeUserLP(userData);
}
updateUserIds();
// ...guest 限制、baseUserRestrictions 初始化...
mPm.onNewUserCreated(userId);
Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
android.Manifest.permission.MANAGE_USERS);
MetricsLogger.count(mContext, isGuest ? TRON_GUEST_CREATED : TRON_USER_CREATED, 1);
} finally {
Binder.restoreCallingIdentity(ident);
}
return userInfo;
}
关键设计 :Work Profile 的创建与普通用户走同一条 初始化链路------
storage.createUserKey(vold 创建 DE/CE 密钥,第六篇)、mPm.prepareUserData、mPm.createNewUser(第五篇的Settings.createNewUserLI)、mPm.onNewUserCreated。区别只在两处:
- 数量限制 :
canAddMoreManagedProfiles只针对isManagedProfile生效,普通用户走isUserLimitReached;- profile 关联 :创建后把
profileGroupId从父用户继承过来,建立 profile ↔ 父用户的绑定关系,并回写父用户的profileGroupId。注意
userInfo.partial = true贯穿整个初始化过程------期间任何查询该用户状态的调用都会看到"半成品",直到createNewUser完成后才置 false。
2.3 数量限制:每用户 1 个,硬编码
源码路径 :frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
java
// Maximum number of managed profiles permitted per user is 1. This cannot be increased
private static final int MAX_MANAGED_PROFILES = 1;
public boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne) {
checkManageUsersPermission("check if more managed profiles can be added.");
if (ActivityManager.isLowRamDeviceStatic()) {
return false;
}
if (!mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_MANAGED_USERS)) {
return false;
}
// Limit number of managed profiles that can be created
final int managedProfilesCount = getProfiles(userId, true).size() - 1;
final int profilesRemovedCount = managedProfilesCount > 0 && allowedToRemoveOne ? 1 : 0;
if (managedProfilesCount - profilesRemovedCount >= MAX_MANAGED_PROFILES) {
return false;
}
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
if (!userInfo.canHaveProfile()) {
return false;
}
int usersCountAfterRemoving = getAliveUsersExcludingGuestsCountLU()
- profilesRemovedCount;
// We allow creating a managed profile in the special case where there is only one user.
return usersCountAfterRemoving == 1
|| usersCountAfterRemoving < UserManager.getMaxSupportedUsers();
}
}
关键设计 :
MAX_MANAGED_PROFILES = 1是硬编码常量 ,不是persist.sys.max_profiles这样的系统属性(原稿此处描述有误)。前置条件有三:非低内存设备、FEATURE_MANAGED_USERS系统特性存在、父用户canHaveProfile()为 true。allowedToRemoveOne参数用于"先删旧 profile 再建新的"场景(MAM 重装 MDM 时的典型操作)。
2.4 最大用户数
源码路径 :frameworks/base/core/java/android/os/UserManager.java
java
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));
}
关键设计 :最大用户数由系统属性
fw.max_users控制,缺省值来自资源config_multiuserMaximumUsers(默认 4)。低内存设备直接返回 1。多用户 UI 是否显示由fw.show_multiuserui属性 +config_enableMultiUserUI资源共同决定。
三、Profile Owner 的设置与管控
Work Profile 创建出来后只是一个"空壳"用户。要让它成为受企业管控的 Work Profile,还需要设置 Profile Owner(设备管理组件)。
3.1 setProfileOwner
源码路径 :frameworks/base/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
java
@Override
public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) {
if (!mHasFeature) {
return false;
}
if (who == null
|| !isPackageInstalledForUser(who.getPackageName(), userHandle)) {
throw new IllegalArgumentException("Component " + who
+ " not installed for userId:" + userHandle);
}
final boolean hasIncompatibleAccountsOrNonAdb =
hasIncompatibleAccountsOrNonAdbNoLock(userHandle, who);
synchronized (this) {
enforceCanSetProfileOwnerLocked(who, userHandle, hasIncompatibleAccountsOrNonAdb);
if (getActiveAdminUncheckedLocked(who, userHandle) == null
|| getUserData(userHandle).mRemovingAdmins.contains(who)) {
throw new IllegalArgumentException("Not active admin: " + who);
}
mOwners.setProfileOwner(who, ownerName, userHandle);
mOwners.writeProfileOwner(userHandle);
Slog.i(LOG_TAG, "Profile owner set: " + who + " on user " + userHandle);
return true;
}
}
关键设计 :调用前提是组件
who已经是该 profile 的 active admin(getActiveAdminUncheckedLocked非空)。enforceCanSetProfileOwnerLocked校验该组件声明了DeviceAdminReceiver并带有USES_POLICY_PROFILE_OWNER能力,且该 profile 尚无 owner。整个操作在synchronized(this)(DPM 全局锁)内完成。
3.2 Owners:按用户持久化 owner
源码路径 :frameworks/base/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
java
void setProfileOwner(ComponentName admin, String ownerName, int userId) {
synchronized (mLock) {
// For a newly set PO, there's no need for migration.
mProfileOwners.put(userId, new OwnerInfo(ownerName, admin,
/* userRestrictionsMigrated =*/ true, /* remoteBugreportUri =*/ null,
/* remoteBugreportHash =*/ null));
mUserManagerInternal.setUserManaged(userId, true);
pushToPackageManagerLocked();
}
}
void removeProfileOwner(int userId) {
synchronized (mLock) {
mProfileOwners.remove(userId);
mUserManagerInternal.setUserManaged(userId, false);
pushToPackageManagerLocked();
}
}
ComponentName getProfileOwnerComponent(int userId) {
synchronized (mLock) {
OwnerInfo profileOwner = mProfileOwners.get(userId);
return profileOwner != null ? profileOwner.admin : null;
}
}
关键设计 :
mProfileOwners是ArrayMap<Integer, OwnerInfo>(userId → owner 信息)。设置 owner 后调用mUserManagerInternal.setUserManaged(userId, true)------这个调用会翻转UserInfo的管控状态,是 Work Profile 从"普通 profile 用户"变成"受管 profile"的关键一步。pushToPackageManagerLocked把 owner 信息同步给 PMS(PMS 据此决定该 profile 下应用可见性策略)。writeProfileOwner把 owner 持久化到/data/system/device_policies.xml。
3.3 管控策略的落点
企业策略(禁用相机、禁用截屏、密码要求、应用挂起等)不集中存储在 DPM 里,而是分散落在三个层面:
ActiveAdmin字段 (第九篇已分析):密码质量、disableCamera、disableScreenCapture等直接挂在DevicePolicyData.mAdminMap的ActiveAdmin上,按用户隔离;UserRestriction:通过DevicePolicyManager.setUserRestriction(key, value, userHandle)落到UserManagerService的 per-user 限制表,由hasUserRestriction(key, userId)查询。创建 Work Profile 时的默认限制就是走这里(createUserInternal末尾的mBaseUserRestrictions);- PMS 状态 :应用可见性(
setPackageHidden)、应用挂起(setApplicationEnabledSetting)等通过 PMS 的 per-userPackageUserState落地(第五篇)。
关键设计 :没有统一的"策略中心"。DPM 的职责是接收 admin 的策略调用 → 校验 admin 身份与权限 → 转发到对应的系统服务 。这也解释了为什么 DPM 的
DevicePolicyData里既有 admin 策略字段,又要调用UserManagerInternal、IPackageManager等其他服务。
3.4 Work Profile 的 UI 表现
Work Profile 的"共享前台"体验由 Launcher 和 SystemUI 实现:
- 应用徽章 :Launcher 为 profile 下的应用图标叠加 profile 徽章(
UserInfo.profileBadge相关资源),徽章颜色由企业可配置(setOrganizationName/setOrganizationColor); - 通知 :SystemUI 通知栏对 profile 应用的通知附加 profile 徽章,且 Work Profile 的通知与父用户的通知在同一个通知栏中并列显示(因为共享前台),不需要"切换到 profile"才能看到;
- 快捷切换 :SystemUI 提供 profile 快捷入口(
mProfileSwitcher相关),允许在"个人/工作"上下文间快速切换,但底层不是switchUser,而是变更当前 profile 上下文。
关键设计 :这些 UI 逻辑不在 framework 服务侧,定制 Work Profile 的外观需要改
packages/apps/Launcher3和frameworks/base/packages/SystemUI。framework 服务侧只负责提供UserInfo、UserManager的查询 API 和ACTION_USER_*广播。
四、跨 Profile 交互
Work Profile 与父用户之间的交互受严格控制,核心 API 是 startActivityAsUser:
源码路径 :frameworks/base/core/java/android/app/ActivityManager.java
java
public int startActivityAsUser(Intent intent, Bundle options,
@Nullable String resultTo, @Nullable ProfilerInfo profilerInfo,
@Nullable Bundle bOptions,
@NonNull UserHandle user) {
return ActivityManagerNative.getDefault().startActivity(...);
// ...实际走 ActivityManagerService.startActivityAsUser
}
跨 profile 启动需要 INTERACT_ACROSS_USERS(signature|privileged|development)权限。没有这个权限的应用,startActivityAsUser 会被 ActivityManager.handleIncomingUser 拦截(第七篇分析的 UserController.handleIncomingUser)。
系统提供的跨 profile 交互通道:
| 通道 | 机制 | 权限要求 |
|---|---|---|
startActivityAsUser |
AMS 按 userId 路由 | INTERACT_ACROSS_USERS |
sendBroadcastAsUser |
PMS per-user IntentResolver | 视广播是否跨 profile 可见 |
ContentProvider |
per-user provider 实例 | INTERACT_ACROSS_USERS |
Context#getSystemServiceForUser |
按 userId 取服务实例 | 系统调用 |
createPackageContextAsUser |
跨 profile 取 Context | INTERACT_ACROSS_USERS |
关键设计 :Work Profile 与父用户共享前台但不共享数据,跨 profile 交互必须显式指定
UserHandle并持有跨用户权限。这是"隔离与协作"的平衡点------企业应用(MAM)通常持有signature权限,可以自由跨 profile;普通第三方应用只能操作自己的 profile。
五、系统定制开发实战
5.1 定制入口
多用户定制的真实入口(基于 AOSP 7 源码结构):
| 定制点 | 位置 | 说明 |
|---|---|---|
| 最大用户数 | 系统属性 fw.max_users |
默认 4,改 system.prop 即可 |
| 多用户 UI | fw.show_multiuserui + config_enableMultiUserUI |
资源 bool 值,OEM 可关 |
| 访客是否 ephemeral | 资源 config_guestUserEphemeral |
控制访客退出后是否清数据 |
| Work Profile 数量 | MAX_MANAGED_PROFILES(硬编码) |
改源码,默认 1 |
| 用户切换钩子 | UserController.java(am 包) |
切换状态机入口 |
| 用户生命周期回调 | UserManagerService 的 ACTION_USER_* 广播 |
监听广播即可 |
| 应用安装策略 | Settings.createNewUserLI(pm 包) |
新用户装哪些应用 |
| 存储初始化 | MountService.createUserKey |
DE/CE 密钥创建 |
注意 :原稿中提到的"修改 UserController.switchUser 添加 vendor hook"思路方向正确,但 AOSP 7 的 UserController 在 services/core/java/com/android/server/am/ 包下,且 switchUser 是私有状态机方法(switchUserLocked),定制时应通过 onBootPhase 注册 UserManager 回调或监听 ACTION_USER_SWITCHED 广播,而非直接改切换逻辑。
5.2 多用户感知的自定义系统服务
java
// 自定义 SystemService 监听用户生命周期(示例,非 AOSP 源码)
public class VendorMultiUserService extends SystemService {
private final class VendorReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
final String action = intent.getAction();
if (Intent.ACTION_USER_ADDED.equals(action)) {
onUserAdded(userId);
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
onUserRemoved(userId);
} else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
onUserSwitched(userId);
}
}
}
@Override
public void onStart() {
publishBinderService("vendor_multi_user", new VendorBinderService());
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
getContext().registerReceiver(new VendorReceiver(), filter,
null /* broadcast permission */, getForegroundHandler());
}
}
private void onUserAdded(int userId) {
// 创建用户专属数据目录(注意:/data/vendor 需要 SELinux 策略支持)
// 初始化用户专属配置
}
private void onUserRemoved(int userId) {
// 清理用户数据
}
private void onUserSwitched(int userId) {
// 刷新配置缓存
}
}
关键设计 :监听
ACTION_USER_ADDED/ACTION_USER_REMOVED/ACTION_USER_SWITCHED广播是多用户感知服务最轻量的方式(ACTION_USER_ADDED在createUserInternal末尾以UserHandle.ALL+MANAGE_USERS权限发送,只有系统服务能收到)。Work Profile 的创建也会发ACTION_USER_ADDED,但不发ACTION_USER_SWITCHED(它不参与用户切换),这是区分"新 profile"和"切换用户"的依据。
5.3 SELinux 注意事项
自定义服务的用户专属数据目录(如 /data/vendor/users/<id>/)需要:
# vendor sepolicy(示例)
typeattr vendor_user_data_file fs_access_2;
# 在 file_contexts 中标签
/data/vendor/users(/.*)? u:object_r:vendor_user_data_file:s0
# 允许 system_server 访问
allow system_server vendor_user_data_file:file { read write open create };
关键设计 :用户隔离在 SELinux 层面靠文件路径中的 userId 实现------不同用户的目录虽然都是
vendor_user_data_file类型,但通过fs_access_2属性和 UID 权限(chown到对应用户的 uid)实现访问控制。AOSP 7 的/data/user/<id>也是同样机制(第六篇已分析),不是靠 neverallow 规则"禁止跨用户访问"。
六、调试指南
6.1 用户管理
bash
# 列出所有用户
adb shell pm list users
# 用户详细状态
adb shell dumpsys user
# 按用户查看应用
adb shell pm list packages --user 10
# 切换用户
adb shell am switch-user 10
6.2 Work Profile 专项
bash
# 查看 profile 与父用户关联
adb shell dumpsys user | grep -A 5 "profile"
# 查看 profile owner
adb shell dumpsys device_policy | grep -A 5 "Profile Owner"
# 给 Work Profile 授予权限
adb shell pm grant --user 10 com.example.app android.permission.CAMERA
# 查看用户限制(DPM 策略落点)
adb shell dumpsys user | grep -A 10 "restrictions"
6.3 核心 dumpsys 命令
bash
adb shell dumpsys user # UserManager 完整状态
adb shell dumpsys activity users # 用户切换状态机
adb shell dumpsys package users # PMS 用户维度状态
adb shell dumpsys device_policy # DPM 完整状态(含 owner、admin、策略)
adb shell dumpsys storage # MountService 存储状态(DE/CE)
adb shell dumpsys notification # 通知(含 profile 通知)
adb shell dumpsys wallpaper # 壁纸(per-user)
6.4 日志过滤
bash
# 用户管理
adb logcat -s UserManagerService
# 用户切换
adb logcat | grep -i "UserController\|switchUser\|USER_SWITCH"
# Work Profile / 设备策略
adb logcat | grep -i "DevicePolicy\|managed.profile\|profile.owner"
# 权限
adb logcat -s PackageManager
6.5 常见问题排查
新建用户/Profile 失败:
bash
# 检查用户数量限制
adb shell dumpsys user | grep -i "limit\|max"
# 检查 FEATURE_MANAGED_USERS
adb shell pm list features | grep managed
# 检查日志
adb logcat | grep -i "Cannot add\|createUser\|max.managed"
Work Profile 策略不生效:
bash
# 确认 profile owner 已设置
adb shell dumpsys device_policy | grep "Profile Owner"
# 确认策略落在 ActiveAdmin
adb shell dumpsys device_policy | grep -A 20 "admin"
# 确认 UserRestriction 已设置
adb shell dumpsys user | grep -A 10 "restrictions"
数据隔离异常:
bash
# 检查 CE/DE 目录
adb shell ls -la /data/user/0/ /data/user_de/0/
adb shell ls -la /data/user/10/ /data/user_de/10/
# 检查 SELinux 标签
adb shell ls -laZ /data/user/10/
# 检查 FBE 密钥状态
adb shell dumpsys storage
七、全系列总结
7.1 关键源码文件索引
| 模块 | 关键文件 | 核心类/方法 |
|---|---|---|
| 用户标识 | core/java/android/content/pm/UserInfo.java |
flags(FLAG_MANAGED_PROFILE=0x20)、profileGroupId |
| 用户句柄 | core/java/android/os/UserHandle.java |
getUserId()、getUid()、PER_USER_RANGE=100000 |
| 客户端 API | core/java/android/os/UserManager.java |
createUser、createProfileForUser、getMaxSupportedUsers |
| 用户管理服务 | services/core/java/com/android/server/pm/UserManagerService.java |
createUserInternalUnchecked、canAddMoreManagedProfiles、MAX_MANAGED_PROFILES |
| 用户切换状态机 | services/core/java/com/android/server/am/UserController.java |
switchUserLocked、handleIncomingUser、finishUserBoot |
| 应用管理 | services/core/java/com/android/server/pm/PackageManagerService.java |
createNewUser、cleanUpUser、enforceCrossUserPermission |
| 包设置 | services/core/java/com/android/server/pm/PackageSettingBase.java |
userState(SparseArray<PackageUserState>) |
| 包持久化 | services/core/java/com/android/server/pm/Settings.java |
createNewUserLI、removeUserLPw、writeRuntimePermissionsForUserLPr |
| 权限状态 | services/core/java/com/android/server/pm/PermissionsState.java |
mPermissions(ArrayMap<String, PermissionData>) |
| 存储 | services/core/java/com/android/server/MountService.java |
createUserKey、destroyUserKey、prepareUserStorage |
| 设备策略 | services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java |
setProfileOwner、ActiveAdmin、DevicePolicyData |
| Owner 持久化 | services/devicepolicy/java/com/android/server/devicepolicy/Owners.java |
mProfileOwners、setProfileOwner、writeProfileOwner |
7.2 开发最佳实践
推荐做法:
-
用 Context API 而非硬编码路径
java// 正确:路径自动按当前用户路由 File file = new File(getFilesDir(), "data.txt"); // 错误:写死 /data/user/0/...,其他用户下直接失败 -
跨用户操作显式指定 UserHandle
java// 正确 sendBroadcastAsUser(intent, UserHandle.of(userId)); startActivityAsUser(intent, null, UserHandle.of(userId)); // 错误:UserHandle.ALL 会发往所有用户,绝大多数场景不该用 -
用 UserHandle 工具方法推导用户信息
java// 正确 int userId = UserHandle.getUserId(Binder.getCallingUid()); // 错误:手动 / 100000,绕过 UserHandle 的常量与校验 -
系统服务按用户隔离状态
java// 正确:SparseArray 按 userId 分桶 private final SparseArray<MyState> mStates = new SparseArray<>(); // 错误:static 单例,所有用户共享同一份状态
常见陷阱:
- 忘记跨用户权限检查 ------所有带
userId参数的系统 API 都应调handleIncomingUser或enforceCrossUserPermission; - 硬编码 userId ------用户 ID 在设备重置后可能变化,应用层用
UserHandle.myUserId(); - 混淆 userId 与 uid ------
userId = uid / 100000,uid = userId * 100000 + appId,appId = uid % 100000; - 在 Work Profile 里用
switchUser------Work Profile 不参与用户切换,跨 profile 操作用startActivityAsUser; - 假设 DPM 有统一策略中心 ------策略分散在
ActiveAdmin、UserRestriction、PMS 三层,排查时要逐层确认。
7.3 全系列要点回顾
| 篇目 | 核心知识 |
|---|---|
| 第一篇 | 多用户演进、架构总览、config_multiuser 开关 |
| 第二篇 | UserInfo/UserHandle/UserManager 数据结构与 API |
| 第三篇 | 用户创建/删除完整链路(createUserInternal/removeUserInternal) |
| 第四篇 | switchUser 流程、UserController 状态机、广播序列 |
| 第五篇 | PackageSettingBase.userState 按用户隔离、createNewUserLI 装系统应用 |
| 第六篇 | CE/DE 目录分离、MountService 委托 vold cryptfs、FBE 密钥 |
| 第七篇 | handleIncomingUser/enforceCrossUserPermission 双入口、NMS/AMS 多用户 |
| 第八篇 | 广播隔离落点在 PMS per-user IntentResolver、mStickyBroadcasts 按用户分桶 |
| 第九篇 | PermissionsState 两级存储、runtime-permissions.xml 按用户持久化 |
| 第十篇 | Work Profile 创建与 Profile Owner、MAX_MANAGED_PROFILES 硬编码、定制入口 |
7.4 关键常量速查
UserHandle 常量(core/java/android/os/UserHandle.java)
├── USER_SYSTEM = 0
├── USER_OWNER = -3(legacy 模式)
├── USER_CURRENT = -2
├── USER_ALL = -1
└── USER_NULL = -10000
UserInfo 标志(core/java/android/content/pm/UserInfo.java)
├── FLAG_PRIMARY = 0x1
├── FLAG_ADMIN = 0x2
├── FLAG_GUEST = 0x4
├── FLAG_RESTRICTED = 0x8
├── FLAG_INITIALIZED = 0x10
├── FLAG_MANAGED_PROFILE = 0x20
└── FLAG_DISABLED = 0x40
UID 计算
├── PER_USER_RANGE = 100000
├── getUserId(uid) = uid / PER_USER_RANGE
├── getAppId(uid) = uid % PER_USER_RANGE
└── getUid(userId, appId) = userId * PER_USER_RANGE + appId
用户数量
├── 最大用户数:fw.max_users(默认 4,config_multiuserMaximumUsers)
├── Work Profile:每父用户 1(MAX_MANAGED_PROFILES 硬编码)
└── MIN_USER_ID = 10(新用户从 10 开始)
系列到此完结。 十篇覆盖了 AOSP 7 多用户系统从数据模型、生命周期、隔离机制到企业应用的完整链路。建议读者结合实际设备用 dumpsys、pm、am 命令验证本篇内容,并对照源码逐行阅读------多用户机制的精髓藏在 userId 参数如何贯穿 UserManagerService → UserController → PMS → PermissionsState 的每个调用链中。