Android7 多用户源码解析(五)应用安装与管理隔离机制

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


一、核心问题

  • 同一个包,不同用户的"启用/停用/已安装"状态存在哪里?
  • 新用户创建后,系统应用是怎么"自动出现在"新用户里的?
  • 删除用户时,"只有这个用户装过"的第三方 APK 会被删掉吗?

答案都围绕一个数据结构:PackageSetting 里按用户维度的 PackageUserState。本篇按"数据结构 → 新用户初始化 → 用户删除清理"的顺序展开,修正原稿中整段虚构的 API(installPackageAsUser、getInstalledPackagesAsUser、INSTALL_ALL_USERS、UserSetting 均不存在)。


二、数据结构:每个包 × 每个用户 的状态

2.1 PackageSetting 与 PackageUserState

源码路径 :frameworks/base/services/core/java/com/android/server/pm/PackageSettingBase.java

java 复制代码
abstract class PackageSettingBase extends SettingBase {
    final String name;
    final String realName;
    File codePath;
    // ... 版本、签名、安装状态等包级字段 ...

    private static final PackageUserState DEFAULT_USER_STATE = new PackageUserState();

    // 核心:按 userId 维度保存每个用户下的包状态
    private final SparseArray<PackageUserState> userState = new SparseArray<PackageUserState>();

    public PackageUserState readUserState(int userId) {
        PackageUserState state = userState.get(userId);
        if (state != null) {
            return state;
        }
        return DEFAULT_USER_STATE;
    }

    private PackageUserState modifyUserState(int userId) {
        PackageUserState state = userState.get(userId);
        if (state == null) {
            state = new PackageUserState();
            userState.put(userId, state);
        }
        return state;
    }

    void setEnabled(int state, int userId, String callingPackage) {
        modifyUserState(userId).enabled = state;
    }
    int getEnabled(int userId) {
        return readUserState(userId).enabled;
    }
    void setInstalled(boolean inst, int userId) {
        modifyUserState(userId).installed = inst;
    }
    boolean getInstalled(int userId) {
        return readUserState(userId).installed;
    }
    boolean getStopped(int userId) {
        return readUserState(userId).stopped;
    }
    // ... setNotLaunched / setHidden / setSuspended / setBlockUninstall /
    //     setEnabledComponents / setDisabledComponents ...
}

PackageUserState(frameworks/base/core/java/android/content/pm/PackageUserState.java)的字段:

java 复制代码
public final class PackageUserState implements Parcelable {
    public int enabled = COMPONENT_ENABLED_STATE_DEFAULT;
    public boolean installed;
    public long ceDataInode;
    public boolean stopped;
    public boolean notLaunched;
    public boolean hidden;
    public boolean suspended;
    public String lastDisableAppCaller;
    public ArraySet<String> enabledComponents;
    public ArraySet<String> disabledComponents;
    public boolean blockUninstall;
    // ... 域名验证、应用链接状态 ...
}

关键设计 :包状态 = 包级字段(codePath、versionCode、签名)+ 每用户 PackageUserState(installed、enabled、stopped、hidden、组件级开关)。APK 本体是跨用户共享的 (一份 /data/app),隔离的是"用户状态 + 数据目录"。这也是 AOSP 7 不支持"同一包不同用户不同版本"的根源------版本号是包级字段。

2.2 包级状态持久化:per-user 的 packages.xml

java 复制代码
// Settings.java
File getUserPackagesStateFile(int userId) {
    return new File(Environment.getDataSystemDirectory(),
            "packages" + File.separator + "packages" + userId + XML_SUFFIX);
}

PMS 把每个用户的包状态单独写到 /data/system/packages/packages<userId>.xml(主用户是 packages.xml)。removeUserLPw 删除时连备份一起删:

java 复制代码
// Settings.java
void removeUserLPw(int userId) {
    for (Entry<String, PackageSetting> entry : mPackages.entrySet()) {
        entry.getValue().removeUser(userId);   // 清每个包的 PackageUserState
    }
    mPreferredActivities.remove(userId);
    File file = getUserPackagesStateFile(userId);
    file.delete();
    file = getUserPackagesStateBackupFile(userId);
    file.delete();
    removeCrossProfileIntentFiltersLPw(userId);
    mRuntimePermissionsPersistence.onUserRemovedLPw(userId);
    writePackageListLPr();
}

关键设计 :per-user 的 packages<id>.xml 意味着"用户 A 对某应用的设置(停用、组件开关、隐停)"完全不落在用户 B 的文件里------文件级隔离,崩溃恢复也按用户独立。


三、新用户:系统应用如何"自动出现"

第三篇已看到创建链路尾部的两个 PMS 调用:mPm.createNewUser(userId) 与 mPm.onNewUserCreated(userId)。

3.1 createNewUser → Settings.createNewUserLI

源码路径 :frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java 复制代码
// Called by UserManagerService
void createNewUser(int userId) {
    synchronized (mInstallLock) {
        mSettings.createNewUserLI(this, mInstaller, userId);
    }
    synchronized (mPackages) {
        scheduleWritePackageRestrictionsLocked(userId);
        scheduleWritePackageListLocked(userId);
        applyFactoryDefaultBrowserLPw(userId);
        primeDomainVerificationsLPw(userId);
    }
}

// Settings.java
void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer,
        int userHandle) {
    String[] volumeUuids;
    String[] names;
    int[] appIds;
    // ...
    synchronized (mPackages) {
        Collection<PackageSetting> packages = mPackages.values();
        for (PackageSetting ps : packages) {
            if (ps.pkg == null || ps.pkg.applicationInfo == null) {
                continue;
            }
            // Only system apps are initially installed.
            ps.setInstalled(ps.isSystem(), userHandle);
            // 累积参数,锁外统一调 installer
            // ...
        }
    }
    for (int i = 0; i < packagesCount; i++) {
        if (names[i] == null) {
            continue;
        }
        final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
        try {
            installer.createAppData(volumeUuids[i], names[i], userHandle, flags, appIds[i],
                    seinfos[i], targetSdkVersions[i]);
        } catch (InstallerException e) {
            Slog.w(TAG, "Failed to prepare app data", e);
        }
    }
    synchronized (mPackages) {
        applyDefaultPreferredAppsLPw(service, userHandle);
    }
}

关键设计 :新用户初始化 = 遍历所有 PackageSetting,只有系统应用(ps.isSystem())在新用户下置 installed=true ------这就是"新用户只有系统应用"的来源。installer.createAppData 通过 Installer 服务(native 层 installd)创建 /data/user/<id>/<pkg> 数据目录。注意 ps.setInstalled(ps.isSystem(), userHandle) 是对每个包 执行的:第三方包 installed=false 但 enabled 等字段保持默认,用户装包后才翻转为 true。

3.2 onNewUserCreated:权限补齐

java 复制代码
// PackageManagerService.java
void onNewUserCreated(final int userId) {
    mDefaultPermissionPolicy.grantDefaultPermissions(userId);
    // If permission review for legacy apps is required, we represent
    // dangerous permissions for such apps as always granted runtime
    // permissions to keep per user flag state whether review is needed.
    if (mPermissionReviewRequired || Build.PERMISSIONS_REVIEW_REQUIRED) {
        updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
                | UPDATE_PERMISSIONS_REPLACE_ALL);
    }
}

关键设计 :新用户创建后,PMS 为新用户跑一遍默认权限策略 + 危险权限(legacy 应用的 review 状态)。运行时权限的授予本身就是 per-user 的(mRuntimePermissionsPersistence),这里补齐的是"策略层"而非"用户授予记录"。

3.3 第三方应用的安装

AOSP 7 的公开安装 API 是 PackageManager.getPackageArchiveInfo / IPackageManager.installPackage(..., userId)------安装请求本身就带 userId ,PMS 安装成功后只对目标用户置 ps.setInstalled(true, userId) 并创建该用户的数据目录。同一 APK 被另一个用户安装时,复用已有 PackageSetting,只改自己的 PackageUserState:

java 复制代码
// PackageSettingBase.java
void setInstalled(boolean inst, int userId) {
    modifyUserState(userId).installed = inst;
}
boolean isAnyInstalled(int[] users) {
    for (int user : users) {
        if (readUserState(user).installed) {
            return true;
        }
    }
    return false;
}

关键设计 :跨用户共享判定靠 isAnyInstalled------只要还有一个用户 installed=true,APK 文件就不删。这是"用户 A 卸载不影响用户 B"的数据基础(见下文 removeUnusedPackagesLPw)。


四、用户删除:PMS 侧的清理

第三篇的删除链路中 mPm.cleanUpUser(this, userHandle) 在这里展开:

java 复制代码
// PackageManagerService.java
void cleanUpUser(UserManagerService userManager, int userHandle) {
    synchronized (mPackages) {
        mDirtyUsers.remove(userHandle);
        mUserNeedsBadging.delete(userHandle);
        mSettings.removeUserLPw(userHandle);          // 清包状态 + 删 packages<id>.xml
        mPendingBroadcasts.remove(userHandle);
        mEphemeralApplicationRegistry.onUserRemovedLPw(userHandle);
        removeUnusedPackagesLPw(userManager, userHandle);
    }
}

/** We're removing userHandle and would like to remove any downloaded packages
 * that are no longer in use by any other user. */
private void removeUnusedPackagesLPw(UserManagerService userManager, final int userHandle) {
    int[] users = userManager.getUserIds();
    for (PackageSetting ps : mSettings.mPackages.values()) {
        if (ps.pkg == null) continue;
        // Skip over if system app
        if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
            continue;
        }
        boolean keep = shouldKeepUninstalledPackageLPr(packageName);
        if (!keep) {
            for (int i = 0; i < users.length; i++) {
                if (users[i] != userHandle && ps.getInstalled(users[i])) {
                    keep = true;      // 其他用户还装着 → 保留
                    break;
                }
            }
        }
        if (!keep) {
            mHandler.post(() -> deletePackageX(packageName, userHandle, 0));
        }
    }
}

关键设计 :删除用户时,非系统包 逐个检查"其他用户是否还在用"(ps.getInstalled(otherUser)),全都不用了才 deletePackageX 删 APK;系统包跳过(设备级资源)。这就是"isAnyInstalled 语义在删除路径上的落地"。


五、跨用户查询与限制

5.1 查询 API 的 userId 参数

AOSP 7 的 PackageManager 查询 API 直接带 userId 参数(不是原稿写的 getXxxAsUser 后缀方法):

java 复制代码
// PackageManagerService.java
public int getApplicationEnabledSetting(String packageName, int userId) {
    if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
    int uid = Binder.getCallingUid();
    enforceCrossUserPermission(uid, userId,
            false /* requireFullPermission */, false /* checkShell */, "get enabled");
    synchronized (mPackages) {
        return mSettings.getApplicationEnabledSettingLPr(packageName, userId);
    }
}

public void setApplicationEnabledSetting(String appPackageName,
        int newState, int flags, int userId, String callingPackage) {
    if (!sUserManager.exists(userId)) return;
    if (callingPackage == null) {
        callingPackage = Integer.toString(Binder.getCallingUid());
    }
    setEnabledSetting(appPackageName, null, newState, flags, userId, callingPackage);
}

关键设计 :跨用户查询/修改受 enforceCrossUserPermission 约束------普通 App 只能操作自己用户,INTERACT_ACROSS_USERS(部分操作)或 INTERACT_ACROSS_USERS_FULL(全部操作)才能越界(第九篇展开)。

5.2 安装限制

DISALLOW_INSTALL_APPS 用户限制在安装路径检查,阻止受限用户(如受限 profile)安装应用;DISALLOW_UNINSTALL_APPS 对应卸载。这两个限制由 UserManager.hasUserRestriction 查询,DPC 可按用户下发(第十篇展开)。


六、本篇总结

  1. 状态结构 :PackageSetting(包级:codePath/version/签名)+ SparseArray<PackageUserState>(用户级:installed/enabled/stopped/hidden/组件开关);
  2. 持久化 :per-user 的 packages<id>.xml,文件级隔离;
  3. 新用户 :createNewUserLI 只把系统包 置 installed=true 并由 installd 建数据目录;onNewUserCreated 补权限策略;
  4. APK 共享 :一份 APK 多用户共享,isAnyInstalled 决定是否可删;删除用户时 removeUnusedPackagesLPw 清理"孤儿"第三方包;
  5. 版本隔离不存在:AOSP 7 同一包所有用户同版本(包级 versionCode)。

下一篇进入存储层:/data/user/<id> 与 _ce/_de 目录如何由 FBE 加密隔离,createAppData 的 native 路径长什么样。

相关推荐
AFinalStone7 小时前
Android7 多用户源码解析(二)核心数据结构深度剖析
系统架构·aosp·多用户
沫璃染墨7 小时前
《从零入门Linux系统篇(五十一):线程篇·四——pthread线程库详解:从线程创建到终止与分离》
linux·运维·服务器·开发语言·c++·系统架构·线程
带金箍的至尊宝7 小时前
系统架构设计师笔记 03:CPU 结构、Cache 与总线怎么考
笔记·系统架构
新鲜势力呀9 小时前
PHP 实战:用户登录频繁失败怎么办?从安全防护到登录系统架构优化完整方案
安全·系统架构·php
励志不掉头发的内向程序员12 小时前
【从零写一个CAD 03】三个 double 值得单独一个类吗:把视图变换抽成 View
开发语言·c++·qt·学习·系统架构
Travis_del12 小时前
系统架构设计师考试大纲:考试说明
系统架构·软考·系统架构设计师
AFinalStone20 小时前
Android7 多用户源码解析(六)数据隔离与存储机制详解
系统架构·aosp·多用户
AFinalStone1 天前
Android7 多用户源码解析(一)功能演进与系统架构总览
系统架构·aosp·多用户·usermanaher
Jmyd01231 天前
智慧文博数字化系统架构设计:从三维采集到平台应用的全链路方案
系统架构