安装来源与更新所有权
本文面向已经读过 安装后注册、应用更新流程 和 Package Visibility 过滤 的读者。本文不重复 PackageInstaller session 的创建,而是追踪安装来源进入 PackageSetting 后如何演变:initiator、originator、installer、update owner 分别代表谁,installer 被卸载时哪些字段保留,以及查询 InstallSourceInfo 时为什么不同调用者会看到不同答案。
1. 四种身份
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallSource.java。
| 字段 | 含义 | 可信边界 |
|---|---|---|
mInitiatingPackageName | 创建安装请求的包 | session/PMS 验证调用身份,可保留其签名 |
mOriginatingPackageName | initiator 声称代表的下载来源 | 源码明确说明框架不验证 |
mInstallerPackageName | installer of record | 可被后续管理 API 修改 |
mUpdateOwnerPackageName | 请求独占后续更新的包 | 受 install flag、denylist 和更新规则限制 |
mOriginalInstallerUid 保存最初创建 session 的 UID,mInstallerPackageUid 对应当前 installer;二者在 installer 被替换或卸载后可能不同。mPackageSource 只描述来源分类,不取代上述身份。
2. 不可变对象
InstallSource 是不可变类。setInstallerPackage、setUpdateOwnerPackageName 和 setIsOrphaned 不修改原对象,而是返回一个新实例;字段相同则直接返回 this。包名通过 String.intern() 复用,常见空状态还共享 EMPTY 和 EMPTY_ORPHANED。
InstallSource setUpdateOwnerPackageName(
@Nullable String updateOwnerPackageName) {
if (Objects.equals(updateOwnerPackageName,
mUpdateOwnerPackageName)) {
return this;
}
return createInternal(mInitiatingPackageName,
mOriginatingPackageName, mInstallerPackageName,
mInstallerPackageUid, mOriginalInstallerUid,
intern(updateOwnerPackageName), ...);
}因此 PackageSetting.setInstallSource() 才是替换当前来源状态的提交点。调用者如果只调用 installSource.setUpdateOwnerPackageName() 而不保存返回值,状态不会变化。
3. 初次安装
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java;符号:扫描结果提交中的 InstallSource 处理。
标准 session 安装会携带非空 InstallSource。PMS 根据 initiating package 查找 PackageSetting,不是 ADB 发起时保存其 PackageSignatures,随后把更新后的不可变对象写入新 package setting。
if (!isInstalledByAdb(installSource.mInitiatingPackageName)) {
PackageSetting ips = mPm.mSettings.getPackageLPr(
installSource.mInitiatingPackageName);
if (ips != null) {
installSource = installSource
.setInitiatingPackageSignatures(ips.getSignatures());
}
}
pkgSetting.setInstallSource(installSource);initiator 签名用于 initiator 被卸载后的受控查询;originator 没有对应签名证明,因为它本来就是 initiator 提供的未验证声明。
4. 请求更新所有权
普通 APK 只有在初次安装携带 INSTALL_REQUEST_UPDATE_OWNERSHIP 时,installer 才可能成为 update owner。以下条件会清除请求:目标包在 denylist、installer 自己是 denylist provider、已有包却未启用 ownership、或新 installer 与旧 owner 不同。
boolean requestOwnership = (request.getInstallFlags()
& INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0;
boolean sameOwner = TextUtils.equals(oldUpdateOwner,
installSource.mInstallerPackageName);
if (!isUpdate) {
if (!requestOwnership || isUpdateOwnershipDenylisted
|| isInstallerUpdateOwnerDenylistProvider) {
installSource = installSource
.setUpdateOwnerPackageName(null);
} else if ((!isUpdateOwnershipEnabled && pkgAlreadyExists)
|| (isUpdateOwnershipEnabled && !sameOwner)) {
installSource = installSource
.setUpdateOwnerPackageName(null);
}
} else if (!sameOwner || !isUpdateOwnershipEnabled) {
installSource = installSource
.setUpdateOwnerPackageName(null);
}更新所有权不是“最后安装者自动拥有”。它必须在初装显式请求,并在后续更新中保持 installer 与 owner 一致;用户允许其他 installer 更新后,ownership 会被清除,而不是转移给新 installer。
5. 系统包 owner
非 session 的 system scan 没有 InstallSource,PMS 从 sysconfig 获取 system app update owner。首次发现 system package 时写入该 owner;OTA 后如果旧 owner 与新 sysconfig 不一致则清除,避免旧配置继续控制新系统镜像。
APEX 不走这一 APK ownership 分支。system app 从普通包变成系统包时,也只有旧 owner 与 sysconfig 一致才保留。
6. Denylist 模型
源码文件:frameworks/base/services/core/java/com/android/server/pm/UpdateOwnershipHelper.java。
合法 denylist provider 必须是 system/updated-system package,声明 PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST,并请求 INSTALL_PACKAGES 或 INSTALL_PACKAGE_UPDATES。XML 中每个 <deny-ownership> 文本是一个不允许启用 update ownership 的目标包,最多读取 500 项。
private final ArrayMap<String, ArraySet<String>>
mUpdateOwnerOptOutsToOwners = new ArrayMap<>(200);map 的 key 是被 denylist 的目标包,value 是贡献该条目的 provider 集合。只有所有 provider 都移除后,目标包才不再 denylisted。provider 包删除时,removeUpdateOwnerDenyList 遍历并移除其贡献。
读取失败返回 null,不会把部分解析结果加入内存;超过 500 项时保留前面的集合并写 warning。
7. Denylist 生效
安装/扫描一个合法 provider 后,PMS 在 handler 上读取资源并注册 denylist。对于列表中已有 update owner、但 sysconfig 没有指定 owner 的包,PMS会清除其 owner。
for (String unownedPackage : listItems) {
PackageSetting setting =
mPm.mSettings.getPackageLPr(unownedPackage);
if (setting != null
&& mPm.getSystemAppUpdateOwnerPackageName(
unownedPackage) == null) {
setting.setUpdateOwnerPackage(null);
}
}denylist 因而不仅影响未来初装请求,也会撤销已有的非-sysconfig ownership。provider 的资源解析与 owner 清理是异步 handler 工作,包提交成功和 denylist 完全生效之间存在时间差。
8. Installer 被卸载
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallSource.java;符号:removeInstallerPackage。
同一个被卸载包可能同时扮演多个角色,清理规则不同:
if (packageName.equals(mInitiatingPackageName)) {
isInitiatingPackageUninstalled = true;
}
if (packageName.equals(originatingPackageName)) {
originatingPackageName = null;
}
if (packageName.equals(installerPackageName)) {
installerPackageName = null;
installerPackageUid = INVALID_UID;
isOrphaned = true;
}
if (packageName.equals(updateOwnerPackageName)) {
updateOwnerPackageName = null;
}initiator 名称和签名被保留,并标记 uninstalled;originator 直接清除;installer 清除并把记录标成 orphaned;update owner 清除。installer attribution tag 也在产生新对象时清空。
9. 持久化
源码文件:frameworks/base/services/core/java/com/android/server/pm/Settings.java。
Settings 在 package 节点保存 installer、installer UID、original installer UID、update owner、attribution tag、package source、orphaned、initiator 和 initiator-uninstalled。启动读取后调用 InstallSource.create() 恢复不可变对象。
<package
installer="com.example.store"
installerUid="10123"
originalInstallerUid="10123"
updateOwner="com.example.store"
installInitiator="com.example.downloader"
packageSource="1" />originating package 和 initiating signatures 也由对应 Settings 结构持久化。磁盘记录只恢复来源事实;包是否仍可见、installer 是否还安装,要在查询时重新判断。
10. 查询脱敏
源码文件:frameworks/base/services/core/java/com/android/server/pm/ComputerEngine.java;符号:getInstallSourceInfo。
公开查询先检查目标包可见性,再分别过滤每个来源字段:installer、initiator、originator 和 update owner 不可见时置 null。originator 还要求调用者拥有 INSTALL_PACKAGES,因为它是更敏感且未经框架验证的信息。
managed user/profile 中,非 system 且非 update owner 的调用者看不到 update owner;源码认为在这些用户里不强制 ownership,暴露该字段没有意义。
initiator 已卸载时,普通 visibility 无法执行。此时只有目标 app 自己且不是 instant app 才能看到 initiator 名称和保存的签名。
11. 主动放弃 owner
relinquishUpdateOwnership 允许 system、shell 或当前 update owner 清除目标包的 owner。PMS 先验证目标包对调用者可见,再比较 caller appId,最后通过 PackageStateMutator 写 null 并安排 Settings 写盘。
这项操作不可撤销地清除当前 ownership;后续普通更新不会自动恢复,只有符合初装/sysconfig规则的流程才能再次建立 owner。
12. 失败定位
| 现象 | 检查点 | 解释 |
|---|---|---|
| 安装器请求 owner 但结果为 null | install flag、denylist、已有包、old owner | owner 只在满足全部初装条件时建立 |
| 更新后 owner 消失 | installer 是否与旧 owner 相同 | 非 owner 更新会清除而非转移 ownership |
InstallSourceInfo 字段为 null | visibility、权限、managed profile | 内部 InstallSource 可能仍保存原值 |
| installer 卸载后 initiator 仍显示 | mIsInitiatingPackageUninstalled | initiator 名称/签名有意保留给目标 app |
| denylist provider 删除后仍 denylisted | 其他 provider contributor | value 是 provider 集合,不是单个 owner |
| 系统包 OTA 后 owner 消失 | 新旧 sysconfig owner 不一致 | PMS 清除陈旧 system ownership |
13. 源码练习
- 画出 downloader、Package Installer 和应用商店分别作为 originator、initiator、installer 时的字段,并指出哪一个由框架明确不验证。
- 构造初装请求带 ownership flag,但目标包在两个 provider 的 denylist 中;依次卸载 provider,判断何时允许新 ownership。
- 从
removeInstallerPackage追踪同一个包同时是 initiator 和 installer 时的字段结果,说明 orphaned 与 initiator-uninstalled 的区别。 - 给定内部 Settings 有完整 InstallSource、普通应用查询只看到 installer,沿
ComputerEngine.getInstallSourceInfo判断其他字段被哪条规则隐藏。
