克隆应用
本文承接 工作资料、多用户安装 和 per-user 包状态,聚焦 Android 17 的 Clone Profile(克隆资料/应用分身)。文章回答:克隆资料如何定义为 profile,如何由 UMS 创建并交给 PMS 初始化,以及“与父用户一起展示/共享媒体”为什么不等于共享 APK 数据或取消跨用户检查。
Clone Profile 的实现核心不是复制 APK,而是创建一个新的 profile userId。全局 PackageSetting 和代码路径仍可复用;目标 profile 有独立的 PackageUserState、应用数据和 UID。UserProperties 再描述展示、凭据、媒体、策略继承和跨 profile 解析行为。
1. 类型属性
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserTypeFactory.java,符号:getDefaultTypeProfileClone
private static UserTypeDetails.Builder
getDefaultTypeProfileClone() {
return new UserTypeDetails.Builder()
.setName(USER_TYPE_PROFILE_CLONE)
.setBaseType(FLAG_PROFILE)
.setMaxAllowed(
getDefaultMaxAllowedSwitchableUsers())
.setMaxAllowedPerParent(1)
.setProfileParentRequired(true)
.setDefaultRestrictions(null)
.setDefaultCrossProfileIntentFilters(
getDefaultCloneCrossProfileIntentFilter())
.setDefaultSecureSettings(
getDefaultNonManagedProfileSecureSettings())
.setDefaultUserProperties(
new UserProperties.Builder()
.setStartWithParent(true)
.setShowInLauncher(
UserProperties
.SHOW_IN_LAUNCHER_WITH_PARENT)
.setShowInSettings(
UserProperties
.SHOW_IN_SETTINGS_WITH_PARENT)
.setInheritDevicePolicy(
UserProperties
.INHERIT_DEVICE_POLICY_FROM_PARENT)
.setUseParentsContacts(true)
.setMediaSharedWithParent(true)
.setCredentialShareableWithParent(true)
.setDeleteAppWithParent(true));
}Clone Profile 是 FLAG_PROFILE,每个父用户最多一个,必须有 profile parent。源码还明确 defaultRestrictions(null),所以它与 Managed Profile 不同,不是通过一组默认 user restriction 建立隔离;克隆行为主要由 UserProperties 和 cross-profile filters 描述。
2. UserProperties
| 属性 | Clone 默认值 | 含义 |
|---|---|---|
startWithParent | true | 父用户启动时可一起启动 |
showInLauncher | WITH_PARENT | 与父用户一起展示 |
showInSettings | WITH_PARENT | 设置界面跟随父用户展示 |
mediaSharedWithParent | true | 媒体共享策略允许与父用户协作 |
credentialShareableWithParent | true | 凭据可与父用户共享 |
inheritDevicePolicy | FROM_PARENT | 继承设备策略属性 |
deleteAppWithParent | true | 父用户删除应用时联动处理 |
crossProfileIntentResolutionStrategy | NO_FILTERING | 使用 clone 专属解析策略字段 |
这些属性不是 PackageUserState 字段,也不会把两个 user 的应用数据合并。它们由 UMS/ActivityManager/Resolver 等消费者分别读取;PMS 仍然保留 per-user package state。
3. 创建入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:createProfileForUserWithThrow
public UserInfo createProfileForUserWithThrow(
@Nullable String name,
@NonNull String userType,
@UserInfoFlag int flags,
@UserIdInt int parentId,
@Nullable String[] disallowedPackages)
throws ServiceSpecificException {
checkCreateUsersPermission(flags);
try {
return createUserInternal(name, userType,
flags, parentId, disallowedPackages);
} catch (UserManager.CheckedUserOperationException e) {
throw e.toServiceSpecificException();
}
}系统或 DPC 通过 UMS 创建 clone profile,而不是让 PMS 直接新增一份 APK。UMS 会根据 USER_TYPE_PROFILE_CLONE 使用 DISALLOW_ADD_CLONE_PROFILE 限制,并检查 parent 和 per-parent 数量。
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:createUserInternalUncheckedNoTracing
final UserTypeDetails details = mUserTypes.get(userType);
flags |= details.getDefaultUserInfoFlags();
final boolean isProfile = details.isProfile();
if (details.isProfileParentRequired()
&& !canAddMoreProfilesToUser(
details, parentId)) {
throwCheckedUserOperationException(
"Cannot add more profiles of type "
+ userType + " for user " + parentId,
USER_OPERATION_ERROR_MAX_USERS);
}
userInfo = new UserInfo(userId, truncatedName,
null, flags, userType);
userInfo.profileGroupId =
parent.info.profileGroupId;创建出的 clone 具有自己的 userId 和 UserInfo,但通过 profileGroupId 与父 profile group 关联。这个 group 关系供跨 profile 查询使用,不是数据目录共享句柄。
4. PMS 初始化
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:创建阶段 PMS 调用
getStorageManagerInternal().createUserStorageKeys(
userId, userInfo.isEphemeral());
mUserDataPreparer.prepareUserData(
userInfo, StorageManager.FLAG_STORAGE_DE);
getLockSettingsInternal().createNewUser(
userId, userInfo.serialNumber);
Set<String> installablePackages =
mSystemPackageInstaller
.getInstallablePackagesForUserType(
userType);
mPm.createNewUser(userId, installablePackages,
disallowedPackages);Clone profile 的数据 key、DE 数据和包状态初始化与其他 profile 一样经过 UMS → PMS。PMS 的 Settings.createNewUserLI 决定哪些 system package 初始 installed;之后为 clone 启用已有 app 仍走 per-user 安装接口。
源码文件:frameworks/base/services/core/java/com/android/server/pm/Settings.java,符号:createNewUserLI
boolean shouldMaybeInstall = ps.isSystem()
&& !ArrayUtils.contains(disallowedPackages,
ps.getPackageName())
&& !ps.getPkgState().isHiddenUntilInstalled();
boolean shouldReallyInstall = shouldMaybeInstall
&& (userTypeInstallablePackages == null
|| userTypeInstallablePackages.contains(
ps.getPackageName()));
ps.setInstalled(shouldReallyInstall, userHandle);“克隆应用”不是在 Settings 中为每个 app 创建第二个 global PackageSetting。同一个 global package 只增加 clone user 的状态,代码路径和签名仍由全局记录管理。
5. 默认过滤器
源码文件:frameworks/base/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java,符号:clone filters
private static final DefaultCrossProfileIntentFilter
CLONE_TO_PARENT_SEND_ACTION =
new DefaultCrossProfileIntentFilter.Builder(
Direction.TO_PARENT, 0x00000018,
false /* letsPersonalDataIntoProfile */)
.addAction(Intent.ACTION_SEND)
.addAction(Intent.ACTION_SEND_MULTIPLE)
.addAction(Intent.ACTION_SENDTO)
.addDataType("*/*")
.build();
private static final DefaultCrossProfileIntentFilter
PARENT_TO_CLONE_SEND_ACTION =
new DefaultCrossProfileIntentFilter.Builder(
Direction.TO_PROFILE, 0x00000018,
false /* letsPersonalDataIntoProfile */)
.addAction(Intent.ACTION_SEND)
.addAction(Intent.ACTION_SEND_MULTIPLE)
.addAction(Intent.ACTION_SENDTO)
.addDataType("*/*")
.build();Clone 的默认过滤器是双向、按 action/type 精确列出的规则。源码还定义了媒体捕获、HTTP/HTTPS view、联系人 pick、电话/SMS、Photo Picker 和 NFC 等不同过滤器;不能把 clone 的 NO_FILTERING 属性解释成“所有 Intent 无条件直通”。
源码文件:frameworks/base/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java,符号:getDefaultCloneProfileFilters
public static List<DefaultCrossProfileIntentFilter>
getDefaultCloneProfileFilters() {
List<DefaultCrossProfileIntentFilter> filters =
new ArrayList<>(List.of(
PARENT_TO_CLONE_SEND_ACTION,
PARENT_TO_CLONE_WEB_VIEW_ACTION,
PARENT_TO_CLONE_PICK_INSERT_ACTION,
PARENT_TO_CLONE_DIAL_DATA,
CLONE_TO_PARENT_MEDIA_CAPTURE,
CLONE_TO_PARENT_SEND_ACTION,
CLONE_TO_PARENT_WEB_VIEW_ACTION,
CLONE_TO_PARENT_VIEW_ACTION,
CLONE_TO_PARENT_PICK_INSERT_ACTION,
CLONE_TO_PARENT_DIAL_DATA,
CLONE_TO_PARENT_SMS_MMS,
CLONE_TO_PARENT_PHOTOPICKER_SELECTION,
PARENT_TO_CLONE_NFC_TAG_DISCOVERED));
filters.add(CLONE_TO_PARENT_PICK_CONTACTS_ACTION);
return filters;
}过滤器列表是 Clone Profile 的实际通信面。每条规则有 direction、flags、action、category 和 data type;解析时仍要结合 source/target user、组件 enabled 状态和包可见性。
6. 包启用与删除联动
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java,符号:installExistingPackageAsUser
if (!pkgSetting.getInstalled(userId)) {
pkgSetting.setInstalled(true, userId);
pkgSetting.setHidden(false, userId);
pkgSetting.setInstallReason(installReason, userId);
pkgSetting.setUninstallReason(
PackageManager.UNINSTALL_REASON_UNKNOWN,
userId);
pkgSetting.setFirstInstallTime(
System.currentTimeMillis(), userId);
mPm.mSettings.writePackageRestrictionsLPr(userId);
mPm.mSettings.writeKernelMappingLPr(pkgSetting);
installed = true;
}克隆 app 的第二份运行实例来自 clone user 的 installed state,而不是第二份 APK。父用户卸载时是否联动 clone,由 UserProperties.deleteAppWithParent 等消费者决定;PMS 的 per-user 状态仍需为两个 user 分别更新。
7. 共享属性
“共享媒体”和“共享凭据”是 profile policy 属性,不是把两个用户的 app data 目录合并。clone app 仍以 clone user UID 运行,包权限、数据目录和 PackageUserState 仍按 userId 存储;共享行为由 MediaProvider、LockSettings、ActivityManager 等服务在各自 API 中实现。
“继承设备策略”同样不是把 DPC 的包状态复制到 clone。DPM/UMS 根据 profile group 和 user properties 决定策略作用范围,PMS 只在安装、可见性和组件查询处消费 user state。
8. 失败边界
| 现象 | 应检查 |
|---|---|
| 无法创建 clone | DISALLOW_ADD_CLONE_PROFILE、type enabled、parent 和 max per parent |
| clone 没有目标应用 | PMS 初始 installable 集合或 installExistingPackageAsUser |
| Intent 未跨资料 | clone filter 是否包含 action/type/direction,组件是否 enabled |
| Intent 过度直通 | resolver 是否错误忽略了 user/profile flags,而非只看 NO_FILTERING 属性 |
| 父用户删除 app 后 clone 仍在 | deleteAppWithParent 消费者与两个 user 的 installed state |
| clone 数据泄漏到父用户 | UID、CE/DE 目录、MediaProvider/LockSettings 共享策略边界 |
Clone Profile 的核心安全边界是“共享体验,不共享身份”:展示、媒体、凭据和策略可以协作,但应用进程、per-user 包状态和数据目录仍有独立 owner。
9. 阅读检查
沿源码复述:createProfileForUserWithThrow(CLONE) → UMS 类型/parent/数量校验 → PMS createNewUser → clone 的 per-user package state → 默认 cross-profile filters → installExistingPackageAsUser 启用具体 app。
然后回答:Clone Profile 是否是第二份 APK?NO_FILTERING 是否允许所有 Intent 无条件跨越?mediaSharedWithParent=true 是否意味着 clone 与父用户共享 app data 目录?正确答案分别是“不是”“不是”“不是”。
