PMS进阶:应用更新、多用户与权限管理深度解析

0 阅读24分钟

引言

在上一篇文章中,我们深入分析了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 User0系统管理员设备所有者
Secondary User10+受限用户家庭成员共享设备
Guest User临时分配临时访客临时借用设备
Restricted Profile子ID受限访问儿童模式
Work Profile10+企业隔离企业应用隔离

用户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架构转存失败,建议直接上传图片文件

图: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)管理

需求

  1. 员工使用个人设备办公
  2. 工作应用与个人应用完全隔离
  3. IT部门可以远程管理工作应用
  4. 支持工作应用的增量更新
  5. 员工离职时可远程擦除工作数据

实现方案

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增强

  1. 应用更新

    • 增量更新算法优化(节省40%流量)
    • 更快的补丁应用速度
    • 更安全的签名验证
  2. 多用户

    • Work Profile启动速度提升40%
    • 更细粒度的数据隔离
    • 工作模式自动暂停
  3. 权限管理

    • 细粒度位置权限(精确/大致)
    • 通知权限独立控制
    • 照片选择器权限
  4. 安全增强

    • 禁用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可能存在差异。 欢迎来我中的个人主页找到更多有用的知识和有趣的产品