引言
在上一篇文章中,我们深入分析了PMS的核心机制——APK安装、签名校验、包信息管理。但这只是PMS能力的冰山一角。
想象这些实际场景:
- 应用商店需要支持增量更新,用户只需下载10MB而非完整的100MB APK
- 企业管理员需要在一台设备上为不同员工创建隔离的工作环境
- 系统应用需要共享UID来访问彼此的私有数据
- 应用需要动态申请权限,用户可以随时撤销
- 用户想在同一设备上登录两个微信账号
这些高级特性的背后,都是PMS的精妙设计。
传统APK安装(100MB) vs 增量更新(仅10MB差异)
单用户系统 vs 多用户完全隔离(数据/权限/应用)
静态权限 vs 运行时动态权限(用户可控)
应用独立沙箱 vs SharedUserId共享数据
单应用实例 vs 应用克隆(双开)
📖 系列前置阅读:建议先阅读第16篇(PMS核心机制),理解APK安装和包管理的基础知识。
本篇将带你深入PMS的高级领域,探索企业级和商业级应用开发的核心技术。
应用更新机制
全量更新 vs 增量更新 vs Patch更新
Android应用的更新方式经历了多次演进:
| 更新方式 | 下载大小 | 实现复杂度 | 适用场景 | Android版本 |
|---|---|---|---|---|
| 全量更新 | 100% | 简单 | 首次安装、大版本更新 | All |
| 增量更新 | 10-30% | 中等 | 常规版本更新 | Android 5.0+ |
| Patch更新 | 1-5% | 复杂 | 紧急修复、热修复 | Android 7.0+ (自定义) |
1. 全量更新流程
全量更新是最简单直接的方式,用户下载完整的APK并覆盖安装:
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
// Android 15: 全量更新实现
public void installPackage(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, int userId) {
// 1. 验证签名一致性
PackageParser.Package newPkg = parsePackage(originPath);
PackageParser.Package oldPkg = mPackages.get(newPkg.packageName);
if (oldPkg != null) {
// 检查签名是否匹配
if (!compareSignatures(oldPkg.mSignatures, newPkg.mSignatures)) {
throw new PackageManagerException(
INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package signatures do not match the previously installed version"
);
}
// 2. 检查版本号递增
if (newPkg.mVersionCode < oldPkg.mVersionCode) {
throw new PackageManagerException(
INSTALL_FAILED_VERSION_DOWNGRADE,
"New version code " + newPkg.mVersionCode +
" is older than installed version " + oldPkg.mVersionCode
);
}
}
// 3. 保留用户数据目录 /data/data/com.example.app
// 删除旧APK和DEX文件,但保留数据
String dataDir = "/data/data/" + newPkg.packageName;
// dataDir不会被删除,应用数据得以保留
// 4. 安装新APK
installNewPackageLocked(newPkg, installFlags, userId);
// 5. 触发应用更新广播
sendPackageChangedBroadcast(newPkg.packageName,
false /*dontKillApp*/,
new ArrayList<>(Collections.singleton(newPkg.packageName)),
userId);
}
关键点:
- ✅ 签名必须一致(防止恶意替换)
- ✅ 版本号必须递增(除非允许降级安装)
- ✅
/data/data/包名目录保留(用户数据不丢失) - ✅ 旧进程会被杀死并重启
2. 增量更新(Delta Update)
增量更新只下载新旧版本的差异,大幅减少流量消耗。Google Play在Android 5.0引入了File-by-File Patching:
工作原理:
服务器端:
old.apk (v1.0, 100MB)
new.apk (v2.0, 105MB)
↓
bsdiff算法计算差异
↓
delta.patch (仅10MB)
客户端:
已安装的old.apk
+
delta.patch (下载)
↓
bspatch算法合成
↓
new.apk (完整100MB APK)
↓
正常安装流程
客户端实现(简化版):
// Android 15: 增量更新客户端实现
public class DeltaUpdateManager {
/**
* 应用增量更新补丁
*
* @param baseApkPath 已安装的旧APK路径
* @param patchFile 差异补丁文件
* @param outputApk 输出的新APK路径
*/
public boolean applyDeltaPatch(String baseApkPath, File patchFile, File outputApk) {
try {
// 1. 验证基础APK的SHA256哈希(确保是预期的版本)
String baseHash = calculateSHA256(new File(baseApkPath));
String expectedHash = extractExpectedHash(patchFile);
if (!baseHash.equals(expectedHash)) {
Log.e(TAG, "Base APK hash mismatch, fallback to full download");
return false; // 回退到全量更新
}
// 2. 使用bspatch算法合成新APK
// 这是一个Native方法,底层调用bsdiff库
int result = nativeApplyPatch(
baseApkPath, // 旧APK
patchFile.getPath(), // 补丁文件
outputApk.getPath() // 输出新APK
);
if (result != 0) {
Log.e(TAG, "Patch apply failed: " + result);
return false;
}
// 3. 验证合成的新APK签名
PackageParser.Package pkg = parsePackage(outputApk.getPath());
if (!verifySignature(pkg)) {
Log.e(TAG, "Patched APK signature verification failed");
outputApk.delete();
return false;
}
// 4. 执行正常的安装流程
installPackage(outputApk.getPath(), observer, flags, installerPackageName);
return true;
} catch (Exception e) {
Log.e(TAG, "Delta update failed", e);
return false; // 失败则回退到全量更新
}
}
// Native层实现(基于bsdiff算法)
private native int nativeApplyPatch(String oldFile, String patchFile, String newFile);
}
增量更新的优势:
- 📉 流量节省70-90%(100MB → 10MB)
- ⚡ 更新速度更快
- 💰 节省服务器带宽成本
Google Play的优化:
- Smart App Updates:分析APK结构,针对资源文件优化
- File-by-File Diff:针对APK内每个文件计算差异
- Archive Diff:先解压APK,再计算差异(更精确)
3. Patch更新(热修复)
Patch更新主要用于紧急Bug修复,无需完整安装流程:
实现方式:
// 热修复框架示例(基于PMS机制)
public class HotfixManager {
/**
* 加载热修复补丁
* 原理:在应用启动时,优先加载patch.dex到ClassLoader
*/
public void loadHotfixPatch(Context context, File patchFile) {
try {
// 1. 验证补丁签名(必须与应用签名一致)
if (!verifyPatchSignature(context, patchFile)) {
throw new SecurityException("Patch signature mismatch");
}
// 2. 复制patch到应用私有目录
File dexDir = context.getDir("hotfix", Context.MODE_PRIVATE);
File patchDex = new File(dexDir, "patch.dex");
copyFile(patchFile, patchDex);
// 3. 插入到ClassLoader的dexElements数组最前面
// 这样patch中的类会优先于原APK中的类被加载
ClassLoader classLoader = context.getClassLoader();
Object dexPathList = ReflectUtil.getField(classLoader, "pathList");
Object[] oldDexElements = (Object[]) ReflectUtil.getField(dexPathList, "dexElements");
// 加载patch.dex
DexClassLoader patchClassLoader = new DexClassLoader(
patchDex.getPath(),
dexDir.getPath(),
null,
classLoader.getParent()
);
Object patchDexPathList = ReflectUtil.getField(patchClassLoader, "pathList");
Object[] patchDexElements = (Object[]) ReflectUtil.getField(patchDexPathList, "dexElements");
// 合并dexElements:patch在前,原APK在后
Object[] newDexElements = new Object[patchDexElements.length + oldDexElements.length];
System.arraycopy(patchDexElements, 0, newDexElements, 0, patchDexElements.length);
System.arraycopy(oldDexElements, 0, newDexElements, patchDexElements.length, oldDexElements.length);
// 替换ClassLoader的dexElements
ReflectUtil.setField(dexPathList, "dexElements", newDexElements);
Log.i(TAG, "Hotfix patch loaded successfully");
} catch (Exception e) {
Log.e(TAG, "Failed to load hotfix patch", e);
}
}
}
热修复的限制:
- ⚠️ 无法修复资源文件(只能修复代码)
- ⚠️ 无法修改AndroidManifest.xml
- ⚠️ 需要重启应用才能生效
- ⚠️ Android 15对类加载器做了更严格的限制
Android 15的改进:
- 引入更安全的Code Transparency机制
- 要求所有DEX文件必须在AndroidManifest中声明
- 禁止未签名的DEX文件加载
多用户系统架构
从Android 4.2开始,Android支持多用户系统,主要用于平板电脑和企业设备。Android 15进一步增强了多用户的性能和隔离性。
多用户概念与架构
用户类型
Android定义了多种用户类型:
| 用户类型 | UserID | 权限范围 | 典型场景 |
|---|---|---|---|
| System User | 0 | 系统管理员 | 设备所有者 |
| Secondary User | 10+ | 受限用户 | 家庭成员共享设备 |
| Guest User | 临时分配 | 临时访客 | 临时借用设备 |
| Restricted Profile | 子ID | 受限访问 | 儿童模式 |
| Work Profile | 10+ | 企业隔离 | 企业应用隔离 |
用户ID分配规则
Android使用UserID来区分不同用户,应用的UID也与UserID绑定:
UID = UserID × 100000 + AppID
示例:
- System User (UserID=0) 中的Chrome (AppID=10086)
UID = 0 × 100000 + 10086 = 10086
- Secondary User (UserID=10) 中的Chrome (AppID=10086)
UID = 10 × 100000 + 10086 = 1010086
结论:不同用户的同一应用,UID不同,数据完全隔离!
PMS中的多用户管理
UserManagerService与PMS协作
// frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
// Android 15: 多用户管理核心实现
public class UserManagerService extends IUserManager.Stub {
/**
* 创建新用户
*/
public UserInfo createUser(String name, int flags) {
// 1. 分配新的UserID
int userId = getNextAvailableId();
// 2. 创建用户数据目录
// /data/user/10/ (对应UserID=10)
// /data/user_de/10/ (设备加密存储)
File userDataDir = Environment.getUserSystemDirectory(userId);
userDataDir.mkdirs();
// 3. 创建用户配置
UserInfo userInfo = new UserInfo(userId, name, flags);
mUsers.put(userId, userInfo);
// 4. 通知PMS安装必要的系统应用
mPm.createNewUser(userId, /* disallowedPackages */ null);
// 5. 持久化用户列表到 /data/system/users/userlist.xml
writeUserListLP();
return userInfo;
}
/**
* 删除用户
*/
public boolean removeUser(int userId) {
if (userId == UserHandle.USER_SYSTEM) {
Log.e(TAG, "Cannot remove system user");
return false;
}
synchronized (mUsersLock) {
UserInfo userInfo = mUsers.get(userId);
if (userInfo == null) return false;
// 1. 停止该用户的所有进程
ActivityManager.getService().stopUser(userId, /* force */ true, null);
// 2. 通知PMS清理该用户的所有应用数据
mPm.cleanUpUser(userId);
// 3. 删除用户数据目录
// /data/user/10/
removeUserState(userId);
// 4. 从用户列表移除
mUsers.remove(userId);
writeUserListLP();
return true;
}
}
}
PMS如何处理多用户应用安装
// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
// Android 15: 多用户应用安装
public class PackageManagerService {
/**
* 为新用户安装系统应用
*/
public void createNewUser(int userId, String[] disallowedPackages) {
// 1. 遍历所有已安装的应用
synchronized (mPackages) {
for (PackageSetting ps : mSettings.mPackages.values()) {
// 2. 判断应用是否需要为新用户安装
if (ps.pkg.isSystem() || ps.getInstalled(UserHandle.USER_SYSTEM)) {
// 系统应用默认为所有用户安装
boolean install = !ArrayUtils.contains(disallowedPackages, ps.name);
if (install) {
// 3. 为新用户创建应用数据目录
// /data/user/10/com.android.chrome
int uid = UserHandle.getUid(userId, ps.appId);
mInstaller.createAppData(
ps.volumeUuid,
ps.name,
userId,
FLAG_STORAGE_CE | FLAG_STORAGE_DE,
uid,
ps.pkg.getSeInfo(),
ps.pkg.getTargetSdkVersion()
);
// 4. 标记应用已为该用户安装
ps.setInstalled(true, userId);
// 5. 授予默认权限
mPermissionManager.grantDefaultPermissions(ps.pkg, userId);
}
}
}
// 6. 持久化更新到packages.xml
mSettings.writePackageRestrictionsLPr(userId);
}
}
/**
* 清理用户的所有应用数据
*/
public void cleanUpUser(int userId) {
synchronized (mPackages) {
// 1. 清理所有应用的用户数据
for (PackageSetting ps : mSettings.mPackages.values()) {
if (ps.getInstalled(userId)) {
// 删除 /data/user/10/com.example.app
mInstaller.destroyAppData(
ps.volumeUuid,
ps.name,
userId,
FLAG_STORAGE_CE | FLAG_STORAGE_DE,
0
);
// 标记应用未为该用户安装
ps.setInstalled(false, userId);
}
}
// 2. 删除用户的packages.xml
mSettings.removeUserLPw(userId);
}
}
}
多用户数据隔离:
# 不同用户的应用数据完全隔离
# System User (UserID=0)
/data/user/0/com.android.chrome/ # Chrome的数据
/data/user/0/com.example.app/ # 用户应用的数据
# Secondary User (UserID=10)
/data/user/10/com.android.chrome/ # Chrome的数据(独立副本)
/data/user/10/com.example.app/ # 用户应用的数据(独立副本)
# 结论:即使是同一个APK,不同用户的数据完全隔离!
工作配置文件(Work Profile)
Work Profile是Android 5.0引入的企业级特性,允许在个人设备上创建隔离的工作环境。
Work Profile架构

图:Work Profile架构展示了个人配置文件和工作配置文件的完全隔离,以及EMM/MDM的管理能力
核心特点:
- 🔒 数据隔离:个人数据(/data/user/0)和工作数据(/data/user/10)完全分离
- 👔 应用隔离:工作应用和个人应用运行在不同的UID空间
- 🛡️ 权限隔离:工作应用无法访问个人联系人、照片等
- 📱 独立认证:工作配置文件可以有独立的密码/生物识别
- 🚫 远程擦除:IT管理员可以远程擦除工作数据,不影响个人数据
Work Profile实现原理
// frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java
// Android 15: Work Profile创建实现
public UserInfo createProfileForUser(String name, int flags, int userId,
String[] disallowedPackages) {
// 1. Work Profile是特殊的"Profile"类型用户
flags |= UserInfo.FLAG_PROFILE; // 标记为Profile
flags |= UserInfo.FLAG_MANAGED_PROFILE; // 工作配置文件标记
// 2. 创建Profile用户
UserInfo profile = createUser(name, flags);
if (profile == null) {
return null;
}
// 3. 关联父用户(个人配置文件)
profile.profileGroupId = userId; // 关联到父用户UserID
mUserRelations.put(profile.id, userId); // 建立关系映射
// 4. 设置Profile特有属性
setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, profile.id);
setUserRestriction(UserManager.DISALLOW_UNINSTALL_APPS, true, profile.id);
// 5. 安装工作应用白名单
mPm.createNewUser(profile.id, disallowedPackages);
// 6. 启动ProfileOwnerService (EMM管理)
DevicePolicyManagerService.getInstance()
.setProfileOwner(deviceOwnerComponent, profile.id);
return profile;
}
跨配置文件通信机制:
Work Profile与Personal Profile之间需要有限的数据共享能力(如分享文件、拨打电话等),Android通过Intent Forwarding实现:
// frameworks/base/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
// Android 15: 跨配置文件Intent转发
public class CrossProfileIntentFilter {
/**
* 处理跨配置文件的Intent
*/
public boolean forwardIntent(Intent intent, int sourceUserId, int targetUserId) {
// 1. 检查Intent是否被允许跨配置文件
if (!isCrossProfileIntentAllowed(intent)) {
Log.w(TAG, "Intent not allowed for cross-profile: " + intent);
return false;
}
// 2. 常见的允许跨配置文件的Intent
String action = intent.getAction();
switch (action) {
case Intent.ACTION_SEND: // 分享文件
case Intent.ACTION_SEND_MULTIPLE: // 分享多个文件
case Intent.ACTION_VIEW: // 查看文件
case Intent.ACTION_DIAL: // 拨打电话
case Intent.ACTION_SENDTO: // 发送短信
// 这些Intent可以跨配置文件
break;
default:
// 其他Intent默认不允许
return false;
}
// 3. 数据URI授权
if (intent.getData() != null) {
// 授予目标配置文件临时读取权限
grantUriPermission(
targetUserId,
intent.getData(),
Intent.FLAG_GRANT_READ_URI_PERMISSION
);
}
// 4. 转发Intent到目标配置文件
ActivityManager.getService().startActivityAsUser(
intent,
targetUserId
);
return true;
}
}
EMM/MDM管理能力:
企业移动管理(EMM)或移动设备管理(MDM)系统可以通过Device Policy API管理Work Profile:
// 企业应用(Device Policy Controller)示例
public class WorkProfileManager {
private DevicePolicyManager mDpm;
private ComponentName mAdminComponent;
/**
* 配置工作配置文件策略
*/
public void setupWorkProfilePolicies() {
// 1. 设置密码策略
mDpm.setPasswordQuality(mAdminComponent,
DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
mDpm.setPasswordMinimumLength(mAdminComponent, 8);
mDpm.setPasswordHistoryLength(mAdminComponent, 5); // 不能重用最近5个密码
// 2. 应用白名单
mDpm.setPermittedInputMethods(mAdminComponent, Arrays.asList(
"com.google.android.inputmethod.latin", // 只允许Google输入法
"com.company.secureime"
));
// 3. 禁止截图
mDpm.setScreenCaptureDisabled(mAdminComponent, true);
// 4. 强制加密
mDpm.setStorageEncryption(mAdminComponent, true);
// 5. 网络配置
mDpm.setGlobalProxy(mAdminComponent, ProxyInfo.buildDirectProxy(
"proxy.company.com", 8080
));
// 6. 设置VPN强制开启
mDpm.setAlwaysOnVpnPackage(mAdminComponent, "com.company.vpn", true);
}
/**
* 远程擦除工作数据
*/
public void wipeWorkProfile() {
// 仅擦除工作配置文件,不影响个人数据
mDpm.wipeData(DevicePolicyManager.WIPE_EXTERNAL_STORAGE);
// UserManagerService会删除Work Profile的所有数据(/data/user/10/)
}
}
Android 15的Work Profile增强:
- ⚡ 性能优化:Work Profile启动速度提升40%
- 🔐 更细粒度的数据隔离:工作应用无法读取个人剪贴板
- 📊 工作/个人使用时间统计分离
- 🌙 工作模式自动暂停:下班后自动暂停工作应用通知
权限管理深度解析
权限是Android安全模型的核心,从Android 6.0引入运行时权限后,权限管理变得更加动态和用户友好。
权限类型与保护级别
Android定义了4种保护级别(protectionLevel):
| 保护级别 | 授予方式 | 示例权限 | 风险等级 |
|---|---|---|---|
| normal | 安装时自动授予 | INTERNET, VIBRATE | 低 |
| dangerous | 运行时动态申请 | CAMERA, LOCATION | 高 |
| signature | 仅同签名应用 | WRITE_SETTINGS | 极高 |
| signatureOrSystem | 系统应用或同签名 | MANAGE_USERS | 极高 |
权限定义与注册
系统权限定义在AOSP源码中:
// frameworks/base/core/res/AndroidManifest.xml (系统权限定义)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
<!-- Dangerous权限:相机 -->
<permission android:name="android.permission.CAMERA"
android:permissionGroup="android.permission-group.CAMERA"
android:protectionLevel="dangerous"
android:label="@string/permlab_camera"
android:description="@string/permdesc_camera" />
<!-- Signature权限:只有系统应用可用 -->
<permission android:name="android.permission.STATUS_BAR"
android:protectionLevel="signature|privileged"
android:label="@string/permlab_statusBar"
android:description="@string/permdesc_statusBar" />
<!-- Normal权限:自动授予 -->
<permission android:name="android.permission.INTERNET"
android:protectionLevel="normal"
android:label="@string/permlab_internet"
android:description="@string/permdesc_internet" />
</manifest>
权限授予流程
// frameworks/base/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
// Android 15: 权限授予核心实现
public class PermissionManagerService {
/**
* 授予权限
*/
public void grantRuntimePermission(String packageName, String permissionName,
int userId) {
// 1. 检查权限是否存在
PermissionInfo permInfo = mRegistry.getPermission(permissionName);
if (permInfo == null) {
throw new IllegalArgumentException("Unknown permission: " + permissionName);
}
// 2. 检查保护级别
int protectionLevel = permInfo.getProtection();
if (protectionLevel != PermissionInfo.PROTECTION_DANGEROUS) {
throw new SecurityException(
"Permission " + permissionName + " is not a runtime permission"
);
}
// 3. 检查应用是否声明了该权限
PackageInfo pkgInfo = mPm.getPackageInfo(packageName, 0, userId);
if (!ArrayUtils.contains(pkgInfo.requestedPermissions, permissionName)) {
throw new IllegalArgumentException(
"Package " + packageName + " has not requested permission " + permissionName
);
}
// 4. 授予权限
synchronized (mLock) {
int uid = pkgInfo.applicationInfo.uid;
UidPermissionState uidState = getUidStateLocked(uid, userId);
// 设置权限已授予标记
uidState.grantPermission(permInfo);
// 5. 更新AppOps (应用操作监控)
mAppOpsManager.setMode(
AppOpsManager.permissionToOpCode(permissionName),
uid,
packageName,
AppOpsManager.MODE_ALLOWED
);
// 6. 通知应用权限变更
sendPermissionChangedBroadcast(packageName, userId);
// 7. 持久化到packages.xml
mSettings.writeRuntimePermissionsForUserLPr(userId);
}
}
/**
* 撤销权限
*/
public void revokeRuntimePermission(String packageName, String permissionName,
int userId) {
synchronized (mLock) {
PackageInfo pkgInfo = mPm.getPackageInfo(packageName, 0, userId);
int uid = pkgInfo.applicationInfo.uid;
UidPermissionState uidState = getUidStateLocked(uid, userId);
// 撤销权限
uidState.revokePermission(permissionName);
// 更新AppOps
mAppOpsManager.setMode(
AppOpsManager.permissionToOpCode(permissionName),
uid,
packageName,
AppOpsManager.MODE_IGNORED // 拒绝访问
);
// 通知应用
sendPermissionChangedBroadcast(packageName, userId);
// 持久化
mSettings.writeRuntimePermissionsForUserLPr(userId);
}
}
}
权限组(Permission Group)
Android将相关权限组织成权限组,用户授予一次即授予整组权限:
// frameworks/base/core/res/AndroidManifest.xml
<!-- 相机权限组 -->
<permission-group android:name="android.permission-group.CAMERA"
android:label="@string/permgrouplab_camera"
android:description="@string/permgroupdesc_camera"
android:priority="400" />
<permission android:name="android.permission.CAMERA"
android:permissionGroup="android.permission-group.CAMERA"
android:protectionLevel="dangerous" />
<!-- 位置权限组 -->
<permission-group android:name="android.permission-group.LOCATION"
android:label="@string/permgrouplab_location"
android:priority="900" />
<permission android:name="android.permission.ACCESS_FINE_LOCATION"
android:permissionGroup="android.permission-group.LOCATION"
android:protectionLevel="dangerous" />
<permission android:name="android.permission.ACCESS_COARSE_LOCATION"
android:permissionGroup="android.permission-group.LOCATION"
android:protectionLevel="dangerous" />
用户授予逻辑:
- 用户授予
ACCESS_FINE_LOCATION时,ACCESS_COARSE_LOCATION也会自动授予 - 用户撤销任一权限时,整个权限组都会被撤销
权限持久化
权限授予状态持久化在用户专属的XML文件中:
# 权限持久化文件
/data/system/users/0/runtime-permissions.xml # System User
/data/system/users/10/runtime-permissions.xml # Secondary User
<!-- runtime-permissions.xml 示例 -->
<runtime-permissions version="1">
<!-- 应用的权限授予状态 -->
<pkg name="com.android.chrome">
<!-- 已授予的危险权限 -->
<permission name="android.permission.CAMERA" granted="true" flags="0" />
<permission name="android.permission.ACCESS_FINE_LOCATION" granted="true" flags="0" />
<!-- 未授予的危险权限 -->
<permission name="android.permission.RECORD_AUDIO" granted="false" flags="0" />
</pkg>
</runtime-permissions>
Android 15的权限增强
1. 更细粒度的位置权限
// Android 15新增:近似位置权限
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 用户可以选择授予"精确位置"或"大致位置" -->
<!-- 应用需要检查实际授予的权限 -->
boolean hasPreciseLocation = checkSelfPermission(ACCESS_FINE_LOCATION) == GRANTED;
boolean hasApproximateLocation = checkSelfPermission(ACCESS_COARSE_LOCATION) == GRANTED;
2. 后台位置权限单独申请
// Android 15强制:后台位置权限必须单独申请
// 第一步:申请前台位置权限
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_CODE);
// 第二步:在获得前台位置权限后,才能申请后台位置权限
if (hasForegroundLocationPermission) {
requestPermissions(
new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION},
REQUEST_CODE_BACKGROUND
);
}
3. 通知权限(Android 13+)
// Android 13+:推送通知需要申请权限
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
// 运行时申请
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissions(
new String[]{Manifest.permission.POST_NOTIFICATIONS},
REQUEST_CODE_NOTIFICATION
);
}
4. 照片/视频选择器权限
// Android 15:更细粒度的媒体访问权限
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
// 替代传统的READ_EXTERNAL_STORAGE,更安全
SharedUserId机制
SharedUserId是Android的一个强大但危险的特性,允许多个应用共享同一个UID,从而访问彼此的私有数据。
SharedUserId的工作原理
UID分配规则回顾
普通应用UID范围: 10000 - 19999 (对于System User)
系统应用UID范围: 1000 - 9999
示例:
- Chrome (独立应用): UID = 10086
- Gmail (独立应用): UID = 10087
- Google Play Services + Google Play Store (SharedUserId): UID = 10088
(两个应用共享同一个UID)
SharedUserId声明
<!-- AndroidManifest.xml -->
<!-- 应用A -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.gms"
android:sharedUserId="com.google.uid.shared">
<!-- Google Play Services -->
</manifest>
<!-- 应用B -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.vending"
android:sharedUserId="com.google.uid.shared">
<!-- Google Play Store -->
</manifest>
关键约束:
- ⚠️ 签名必须一致:所有声明同一SharedUserId的应用必须使用相同的签名
- ⚠️ 首次安装后无法修改:一旦应用安装后,无法更改SharedUserId
- ⚠️ UID分配唯一性:同一SharedUserId的所有应用共享同一个UID
PMS如何处理SharedUserId
// frameworks/base/services/core/java/com/android/server/pm/Settings.java
// Android 15: SharedUserId处理
public class Settings {
// SharedUserId -> UID 映射表
private final ArrayMap<String, SharedUserSetting> mSharedUsers = new ArrayMap<>();
/**
* 获取或创建SharedUserId对应的UID
*/
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
SharedUserSetting s = mSharedUsers.get(name);
if (s != null) {
// SharedUserId已存在,返回已分配的UID
if (s.userId == uid) {
return s;
}
// UID冲突,异常情况
return null;
}
// 创建新的SharedUserSetting
s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
s.userId = uid;
// 如果uid为-1,自动分配新UID
if (uid < 0) {
int newUid = newUserIdLPw(s);
s.userId = newUid;
}
mSharedUsers.put(name, s);
return s;
}
/**
* 安装使用SharedUserId的应用
*/
PackageSetting installPackageWithSharedUserIdLPw(
PackageParser.Package pkg, int userId) {
String sharedUserId = pkg.mSharedUserId;
// 1. 获取或创建SharedUserSetting
SharedUserSetting sharedUser = addSharedUserLPw(
sharedUserId,
-1, // 自动分配UID
pkg.applicationInfo.flags,
pkg.applicationInfo.privateFlags
);
// 2. 验证签名一致性
if (sharedUser.signatures.mSigningDetails != null) {
// SharedUserId已被其他应用使用,检查签名
if (!pkg.mSigningDetails.checkCapability(
sharedUser.signatures.mSigningDetails,
SigningDetails.CertCapabilities.INSTALLED_DATA)) {
throw new PackageManagerException(
INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
"Package " + pkg.packageName +
" has mismatched uid " + sharedUserId +
" signature"
);
}
} else {
// 第一个使用此SharedUserId的应用,设置签名
sharedUser.signatures.mSigningDetails = pkg.mSigningDetails;
}
// 3. 创建PackageSetting并关联SharedUserSetting
PackageSetting pkgSetting = new PackageSetting(
pkg.packageName,
pkg.codePath,
sharedUser.userId, // 使用SharedUserId的UID
pkg.versionCode
);
pkgSetting.sharedUser = sharedUser;
sharedUser.addPackage(pkgSetting);
return pkgSetting;
}
}
SharedUserId的数据共享
使用SharedUserId的应用可以访问彼此的私有数据目录:
// 应用A (com.google.android.gms, SharedUserId="com.google.uid.shared")
// UID = 10088
// 应用B (com.android.vending, SharedUserId="com.google.uid.shared")
// UID = 10088 (相同!)
// 数据目录访问权限:
// /data/data/com.google.android.gms/ (UID 10088可读写)
// /data/data/com.android.vending/ (UID 10088可读写)
// 应用B可以直接读取应用A的数据
Context appAContext = createPackageContext("com.google.android.gms", 0);
File appADataFile = new File(appAContext.getFilesDir(), "shared_data.txt");
String data = readFile(appADataFile); // 成功读取!
SharedUserId的安全风险
风险示例:恶意应用伪造SharedUserId
1. 攻击者分析目标应用的SharedUserId: "com.company.shared"
2. 攻击者创建恶意应用,声明相同的SharedUserId
3. 攻击者尝试使用目标应用的签名(无法获取)
4. 安装失败:签名不匹配!
结论:SharedUserId的签名校验机制阻止了恶意应用共享UID
Android 15对SharedUserId的限制:
- ⚠️ 官方强烈不推荐使用SharedUserId(deprecated)
- ⚠️ 新应用无法声明SharedUserId(targetSdkVersion >= 29)
- ⚠️ 推荐使用ContentProvider或FileProvider进行数据共享
应用克隆技术
应用克隆允许用户在同一设备上运行同一应用的多个实例,如同时登录两个微信账号。
应用克隆的实现方式
方式1:基于多用户机制
// Android的应用克隆本质是创建一个隔离的"Clone User"
// 原理类似Work Profile,但对用户透明
public class AppCloneManager {
/**
* 克隆应用
*/
public boolean cloneApp(String packageName) {
// 1. 创建Clone User (特殊的Profile类型)
UserInfo cloneUser = mUserManager.createProfileForUser(
"Clone",
UserInfo.FLAG_PROFILE | UserInfo.FLAG_CLONE_PROFILE,
UserHandle.USER_SYSTEM, // 关联到主用户
null
);
if (cloneUser == null) {
return false;
}
// 2. 为Clone User安装应用
mPm.installExistingPackageAsUser(
packageName,
cloneUser.id,
PackageManager.INSTALL_ALL_USERS,
PackageManager.INSTALL_REASON_USER
);
// 3. 创建独立的数据目录
// /data/user/100/com.tencent.mm (克隆的微信数据)
// /data/user/0/com.tencent.mm (原始微信数据)
// 4. 在Launcher中显示克隆应用图标(带标记)
broadcastClonedAppInstalled(packageName, cloneUser.id);
return true;
}
/**
* 启动克隆的应用
*/
public void launchClonedApp(String packageName, int cloneUserId) {
Intent intent = mPm.getLaunchIntentForPackage(packageName);
if (intent != null) {
// 在Clone User的上下文中启动应用
mContext.startActivityAsUser(intent, UserHandle.of(cloneUserId));
}
}
}
方式2:虚拟化容器技术
一些第三方应用克隆工具(如平行空间)使用更激进的技术:
// 虚拟化容器原理(简化版)
public class VirtualAppContainer {
/**
* 在虚拟容器中启动应用
*/
public void launchAppInContainer(String packageName) {
// 1. Hook系统API
hookSystemAPIs();
// 2. 伪造应用包名
// 原始应用: com.tencent.mm
// 容器内应用: com.virtual.container.mm (不同的包名)
String virtualPackageName = "com.virtual.container." + packageName.hashCode();
// 3. 映射数据目录
// 原始路径: /data/data/com.tencent.mm
// 虚拟路径: /data/data/com.virtual.container/apps/com.tencent.mm
redirectDataDirectory(packageName, virtualPackageName);
// 4. 拦截Binder调用
// 应用调用ActivityManagerService时,拦截并伪造返回值
interceptBinderCalls();
// 5. 启动应用
startActivity(virtualPackageName);
}
/**
* Hook系统API (使用反射或Native Hook)
*/
private void hookSystemAPIs() {
// Hook PackageManager.getPackageInfo()
// Hook ActivityManager.getRunningAppProcesses()
// Hook ContextImpl.getFilesDir()
// ... 拦截几十个系统API
}
}
虚拟容器的问题:
- ⚠️ 稳定性差:系统API变化会导致崩溃
- ⚠️ 性能开销:每次调用都需要拦截和转换
- ⚠️ 兼容性差:部分应用检测到虚拟环境会拒绝运行
- ⚠️ 安全风险:Hook系统API可能被恶意利用
Android官方的应用克隆支持
Android 12开始,AOSP提供了官方的应用克隆API:
// frameworks/base/core/java/android/os/UserManager.java
// Android 15: 官方应用克隆API
public class UserManager {
/**
* 创建应用的克隆配置文件
*/
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public UserHandle createCloneProfile(String packageName) {
try {
UserInfo cloneUser = mService.createProfileForUserEvenWhenDisallowed(
"Clone: " + packageName,
UserInfo.FLAG_PROFILE | UserInfo.FLAG_CLONE_PROFILE,
mUserId,
new String[]{packageName} // 仅安装此应用
);
return cloneUser.getUserHandle();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* 删除克隆配置文件
*/
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public boolean removeCloneProfile(UserHandle cloneUser) {
try {
return mService.removeUser(cloneUser.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
官方克隆的优势:
- ✅ 稳定性高:基于Android多用户机制
- ✅ 性能好:无需Hook,直接使用系统API
- ✅ 安全性强:完整的权限隔离
- ✅ 兼容性好:应用无法检测到是克隆环境
实战案例:构建企业应用管理系统
让我们通过一个完整的案例,整合本文讲解的所有高级特性。
场景:企业BYOD(Bring Your Own Device)管理
需求:
- 员工使用个人设备办公
- 工作应用与个人应用完全隔离
- IT部门可以远程管理工作应用
- 支持工作应用的增量更新
- 员工离职时可远程擦除工作数据
实现方案
1. 创建Work Profile
// EMM应用 (Enterprise Mobility Management)
public class EnterpriseManager {
private DevicePolicyManager mDpm;
private ComponentName mAdminComponent;
/**
* 初始化Work Profile
*/
public boolean setupWorkProfile(Context context) {
UserManager um = context.getSystemService(UserManager.class);
// 1. 创建Work Profile
UserInfo workProfile = um.createProfileForUser(
"Work",
UserInfo.FLAG_MANAGED_PROFILE,
UserHandle.myUserId(),
null
);
if (workProfile == null) {
Log.e(TAG, "Failed to create work profile");
return false;
}
// 2. 设置Profile Owner (授予管理权限)
mDpm.setProfileOwner(mAdminComponent, workProfile.id);
// 3. 启动Work Profile
um.startProfile(workProfile.getUserHandle());
// 4. 配置企业策略
configureWorkProfilePolicies();
return true;
}
/**
* 配置Work Profile策略
*/
private void configureWorkProfilePolicies() {
// 密码策略
mDpm.setPasswordQuality(mAdminComponent,
DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
mDpm.setPasswordMinimumLength(mAdminComponent, 8);
mDpm.setPasswordExpirationTimeout(mAdminComponent,
TimeUnit.DAYS.toMillis(90)); // 90天后过期
// 应用限制
mDpm.setPermittedAccessibilityServices(mAdminComponent, Arrays.asList(
"com.company.accessibility" // 仅允许公司的辅助功能服务
));
// 禁止安装未知来源应用
mDpm.addUserRestriction(mAdminComponent,
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
// 强制VPN
mDpm.setAlwaysOnVpnPackage(mAdminComponent, "com.company.vpn", true);
// 禁止USB调试
mDpm.addUserRestriction(mAdminComponent,
UserManager.DISALLOW_USB_FILE_TRANSFER);
}
/**
* 安装企业应用(支持增量更新)
*/
public void installEnterpriseApp(String apkUrl, String previousVersion) {
// 1. 检查是否可以增量更新
if (previousVersion != null && supportsDeltaUpdate(apkUrl)) {
// 下载增量补丁 (仅10MB而非100MB)
File patchFile = downloadDeltaPatch(apkUrl, previousVersion);
// 应用补丁
DeltaUpdateManager deltaManager = new DeltaUpdateManager();
File baseApk = getInstalledApk(extractPackageName(apkUrl));
File newApk = new File(getCacheDir(), "updated.apk");
if (deltaManager.applyDeltaPatch(
baseApk.getPath(), patchFile, newApk)) {
// 安装新APK
installApk(newApk);
} else {
// 回退到全量更新
downloadAndInstallFullApk(apkUrl);
}
} else {
// 全量下载安装
downloadAndInstallFullApk(apkUrl);
}
}
/**
* 远程擦除Work Profile数据
*/
public void remoteWipeWorkProfile() {
// 仅擦除Work Profile,不影响个人数据
mDpm.wipeData(
DevicePolicyManager.WIPE_EXTERNAL_STORAGE |
DevicePolicyManager.WIPE_RESET_PROTECTION_DATA
);
Log.i(TAG, "Work profile wiped successfully");
// UserManagerService会删除/data/user/[workProfileId]/下的所有数据
}
}
2. 权限动态管理
// 企业应用需要动态权限管理
public class WorkAppPermissionManager {
/**
* 根据企业策略自动授予权限
*/
public void grantRequiredPermissions(String packageName) {
// 企业策略:CRM应用自动授予联系人权限
if (packageName.equals("com.company.crm")) {
PermissionManager pm = getSystemService(PermissionManager.class);
pm.grantRuntimePermission(
packageName,
Manifest.permission.READ_CONTACTS,
UserHandle.myUserId()
);
pm.grantRuntimePermission(
packageName,
Manifest.permission.CALL_PHONE,
UserHandle.myUserId()
);
}
// 企业策略:销售应用自动授予位置权限
if (packageName.equals("com.company.sales")) {
PermissionManager pm = getSystemService(PermissionManager.class);
pm.grantRuntimePermission(
packageName,
Manifest.permission.ACCESS_FINE_LOCATION,
UserHandle.myUserId()
);
}
}
/**
* 监控权限使用情况
*/
public void monitorPermissionUsage() {
AppOpsManager appOps = getSystemService(AppOpsManager.class);
// 监听位置权限使用
appOps.startWatchingMode(
AppOpsManager.OPSTR_FINE_LOCATION,
null, // 所有应用
new AppOpsManager.OnOpChangedListener() {
@Override
public void onOpChanged(String op, String packageName) {
// 记录到企业日志系统
logPermissionUsage(packageName, op, System.currentTimeMillis());
}
}
);
}
}
3. 应用使用统计
// 统计员工的工作应用使用情况
public class WorkAppUsageTracker {
/**
* 获取Work Profile应用使用统计
*/
public Map<String, Long> getWorkAppUsageStats(int workProfileUserId) {
UsageStatsManager usageStats = getSystemService(UsageStatsManager.class);
long endTime = System.currentTimeMillis();
long startTime = endTime - TimeUnit.DAYS.toMillis(7); // 最近7天
// 查询Work Profile的应用使用情况
List<UsageStats> stats = usageStats.queryUsageStats(
UsageStatsManager.INTERVAL_DAILY,
startTime,
endTime
);
Map<String, Long> usageMap = new HashMap<>();
for (UsageStats stat : stats) {
// 过滤出Work Profile的应用 (UID范围检查)
int uid = getPackageUid(stat.getPackageName(), workProfileUserId);
if (UserHandle.getUserId(uid) == workProfileUserId) {
usageMap.put(stat.getPackageName(), stat.getTotalTimeInForeground());
}
}
return usageMap;
}
/**
* 生成使用报告
*/
public void generateUsageReport() {
Map<String, Long> usage = getWorkAppUsageStats(getWorkProfileUserId());
StringBuilder report = new StringBuilder("Work App Usage Report:\n");
for (Map.Entry<String, Long> entry : usage.entrySet()) {
long minutes = TimeUnit.MILLISECONDS.toMinutes(entry.getValue());
report.append(String.format("- %s: %d minutes\n",
entry.getKey(), minutes));
}
// 上传到企业服务器
uploadReportToServer(report.toString());
}
}
总结
本文深入探讨了PMS的高级特性,这些机制共同构成了Android强大的应用管理能力:
核心要点回顾
1. 应用更新机制
| 更新方式 | 流量消耗 | 适用场景 |
|---|---|---|
| 全量更新 | 100% | 大版本更新、首次安装 |
| 增量更新 | 10-30% | 常规版本更新 |
| Patch更新 | 1-5% | 紧急Bug修复 |
关键技术:bsdiff/bspatch算法、签名校验、回退机制
2. 多用户系统
UID = UserID × 100000 + AppID
不同用户的应用数据完全隔离:
/data/user/0/com.app (System User)
/data/user/10/com.app (Secondary User)
关键技术:UserManagerService、多用户UID分配、数据目录隔离
3. Work Profile
- 🔒 个人/工作数据完全隔离
- 👔 EMM/MDM远程管理能力
- 🚫 远程擦除工作数据
- 🌐 跨配置文件Intent转发
关键技术:Managed Profile、Intent Forwarding、Device Policy API
4. 权限管理
| 权限类型 | 授予时机 | 用户控制 |
|---|---|---|
| Normal | 安装时 | ❌ |
| Dangerous | 运行时 | ✅ |
| Signature | 安装时(同签名) | ❌ |
关键技术:PermissionManagerService、AppOps、运行时权限
5. SharedUserId
- ✅ 允许应用共享UID和数据
- ⚠️ 必须使用相同签名
- ⚠️ Android 15已废弃,不推荐使用
关键技术:SharedUserSetting、签名验证、UID共享
6. 应用克隆
| 实现方式 | 稳定性 | 性能 | 兼容性 |
|---|---|---|---|
| 多用户机制 | 高 | 优 | 好 |
| 虚拟容器 | 低 | 差 | 差 |
关键技术:Clone Profile、数据目录隔离、独立UID分配
Android 15的PMS增强
-
应用更新
- 增量更新算法优化(节省40%流量)
- 更快的补丁应用速度
- 更安全的签名验证
-
多用户
- Work Profile启动速度提升40%
- 更细粒度的数据隔离
- 工作模式自动暂停
-
权限管理
- 细粒度位置权限(精确/大致)
- 通知权限独立控制
- 照片选择器权限
-
安全增强
- 禁用SharedUserId(新应用)
- 强制Code Transparency
- DEX文件签名验证
实战应用场景
- 📱 应用商店:增量更新减少流量成本
- 🏢 企业管理:Work Profile隔离工作数据
- 🔐 安全应用:SharedUserId共享密钥数据
- 👥 应用克隆:官方Clone Profile实现双开
- 🛡️ 权限控制:动态授予/撤销危险权限
参考资料
AOSP源码路径(Android 15)
# PMS核心实现
frameworks/base/services/core/java/com/android/server/pm/
├── PackageManagerService.java # PMS核心服务
├── Settings.java # packages.xml读写
├── InstallPackageHelper.java # 安装辅助
└── PackageInstallerSession.java # 安装Session
# 多用户管理
frameworks/base/services/core/java/com/android/server/pm/
├── UserManagerService.java # 多用户管理
└── UserSystemPackageInstaller.java # 用户应用安装
# 权限管理
frameworks/base/services/core/java/com/android/server/pm/permission/
├── PermissionManagerService.java # 权限管理服务
├── UidPermissionState.java # UID权限状态
└── PermissionRegistry.java # 权限注册表
# 签名验证
frameworks/base/core/java/android/util/apk/
├── ApkSignatureVerifier.java # 签名校验
└── SigningDetails.java # 签名详情
# 增量更新
frameworks/base/services/core/jni/
└── com_android_server_pm_PackageManagerService.cpp # bspatch Native实现
官方文档
调试命令
# 多用户管理
adb shell pm list users # 列出所有用户
adb shell pm create-user "TestUser" # 创建新用户
adb shell pm remove-user 10 # 删除用户
# 权限管理
adb shell pm grant <package> <permission> # 授予权限
adb shell pm revoke <package> <permission> # 撤销权限
adb shell dumpsys package <package> | grep permission # 查看应用权限
# SharedUserId查询
adb shell dumpsys package | grep sharedUserId
# Work Profile
adb shell pm create-profile "Work" # 创建Work Profile
adb shell dpm set-profile-owner <component> # 设置Profile Owner
至此,PMS的核心机制和高级特性已全部揭晓。从应用安装到更新、从多用户隔离到权限管理、从SharedUserId到应用克隆,PMS作为Android的"户籍管理局",管理着应用的完整生命周期。
下一站,我们将前往WMS,探索Android窗口管理的奥秘! 🚀
系列文章
本文基于Android 15 (API Level 35)源码分析,不同厂商的定制ROM可能存在差异。 欢迎来我中的个人主页找到更多有用的知识和有趣的产品