Android7 多用户源码解析(十)Work Profile 企业模式与开发实战指南

系列目录 :第一篇:功能演进与系统架构总览 | 第二篇:核心数据结构深度剖析 | 第三篇:用户创建与删除流程全链路分析 | 第四篇:用户切换机制源码剖析 | 第五篇:应用安装与管理隔离机制 | 第六篇:数据隔离与存储机制详解 | 第七篇:系统服务的多用户感知处理 | [第八篇:广播分发与 Intent 路由的用户隔离](#第八篇:广播分发与 Intent 路由的用户隔离) | 第九篇:权限系统与多用户的交互机制 | [第十篇:Work Profile 企业模式与开发实战指南](#第十篇:Work Profile 企业模式与开发实战指南)


Work Profile(工作配置)是多用户体系里最"特殊"的用户类型:它和主用户共享同一个启动器、同一个锁屏、同一个前台会话 ,但在数据、应用、权限、策略上又是完全独立的第二个用户空间。企业应用(MAM)用它隔离工作数据,setUserRestriction 用它禁用相机、截屏、安装来源等能力。

本篇回答三个问题:

  1. Work Profile 在 UserManagerService 里如何创建?为什么每个用户最多只能有 1 个?
  2. Profile Owner 是如何被设置并持久化的?管控策略落在哪一层?
  3. 系统开发者做多用户定制时的真实入口在哪,实战中如何调试?

需要说明: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。区别只在两处:

  1. 数量限制 :canAddMoreManagedProfiles 只针对 isManagedProfile 生效,普通用户走 isUserLimitReached;
  2. 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 里,而是分散落在三个层面:

  1. ActiveAdmin 字段 (第九篇已分析):密码质量、disableCamera、disableScreenCapture 等直接挂在 DevicePolicyData.mAdminMap 的 ActiveAdmin 上,按用户隔离;
  2. UserRestriction :通过 DevicePolicyManager.setUserRestriction(key, value, userHandle) 落到 UserManagerService 的 per-user 限制表,由 hasUserRestriction(key, userId) 查询。创建 Work Profile 时的默认限制就是走这里(createUserInternal 末尾的 mBaseUserRestrictions);
  3. PMS 状态 :应用可见性(setPackageHidden)、应用挂起(setApplicationEnabledSetting)等通过 PMS 的 per-user PackageUserState 落地(第五篇)。

关键设计 :没有统一的"策略中心"。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 开发最佳实践

推荐做法:

  1. 用 Context API 而非硬编码路径

    java 复制代码
    // 正确:路径自动按当前用户路由
    File file = new File(getFilesDir(), "data.txt");
    // 错误:写死 /data/user/0/...,其他用户下直接失败
  2. 跨用户操作显式指定 UserHandle

    java 复制代码
    // 正确
    sendBroadcastAsUser(intent, UserHandle.of(userId));
    startActivityAsUser(intent, null, UserHandle.of(userId));
    // 错误:UserHandle.ALL 会发往所有用户,绝大多数场景不该用
  3. 用 UserHandle 工具方法推导用户信息

    java 复制代码
    // 正确
    int userId = UserHandle.getUserId(Binder.getCallingUid());
    // 错误:手动 / 100000,绕过 UserHandle 的常量与校验
  4. 系统服务按用户隔离状态

    java 复制代码
    // 正确:SparseArray 按 userId 分桶
    private final SparseArray<MyState> mStates = new SparseArray<>();
    // 错误:static 单例,所有用户共享同一份状态

常见陷阱:

  1. 忘记跨用户权限检查 ------所有带 userId 参数的系统 API 都应调 handleIncomingUser 或 enforceCrossUserPermission;
  2. 硬编码 userId ------用户 ID 在设备重置后可能变化,应用层用 UserHandle.myUserId();
  3. 混淆 userId 与 uid ------userId = uid / 100000,uid = userId * 100000 + appId,appId = uid % 100000;
  4. 在 Work Profile 里用 switchUser ------Work Profile 不参与用户切换,跨 profile 操作用 startActivityAsUser;
  5. 假设 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 的每个调用链中。

相关推荐
AFinalStone2 小时前
Android7 多用户源码解析(九)权限系统与多用户的交互机制
系统架构·aosp·多用户
数字新视界3 小时前
国产动环系统技术原理与应用实践的深入剖析
嵌入式硬件·物联网·系统架构·机房管理·动力与环境监控系统
m0_5873830014 小时前
深圳24小时自助健身房解决方案实战:从系统架构到部署指南
java·人工智能·spring boot·spring·系统架构·需求分析
m0_5873830017 小时前
西安同城拼车软件开发实战指南:从零搭建高效系统
人工智能·小程序·数据挖掘·系统架构·需求分析
励志不掉头发的内向程序员21 小时前
【从零写一个CAD 02】画完第一条线之后:实体容器、Esc 取消,和按了没反应的键盘
开发语言·c++·qt·学习·系统架构
Cicada1287 天前
库存消息消费的正确性设计——从幂等窗口到批量流水线
分布式·系统架构
彧azz7 天前
操作系统时间管理与系统核心板块学习总结
c语言·笔记·学习·系统架构
风123456789~7 天前
【架构专栏】第15章 面向服务架构设计 2/3
系统架构
风123456789~7 天前
【架构专栏】第15章 面向服务架构设计 1/3
系统架构