应用元数据文件
本文面向已经读过 安装来源与更新所有权、PackageProperty 索引 和 安装后注册 的读者。本文讨论的是应用元数据文件(app metadata file),不是 Manifest <meta-data> 标签,也不是 PackageProperty。重点是 Android 17 中 metadata 路径和来源如何写入 PackageSetting,安装器提供的 metadata 为什么优先于 APK 内 metadata,以及 GET_APP_METADATA 查询失败时应该看哪一层。
1. 来源优先级
源码文件:frameworks/base/core/java/android/content/pm/PackageManager.java。
Android 17 定义四种来源:
public static final int APP_METADATA_SOURCE_UNKNOWN = 0;
public static final int APP_METADATA_SOURCE_APK = 1;
public static final int APP_METADATA_SOURCE_INSTALLER = 2;
public static final int APP_METADATA_SOURCE_SYSTEM_IMAGE = 3;API 文档明确规定:APK 可以自带 metadata,但安装器在安装时提供 metadata 时,安装器版本优先。UNKNOWN 既表示没有文件,也表示来源尚未识别;不能把它解释成“文件一定不存在”。
2. 元数据状态
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java。
metadata 由两个全局包字段描述:
private String mAppMetadataFilePath;
private int mAppMetadataSource =
PackageManager.APP_METADATA_SOURCE_UNKNOWN;路径和来源不在 PackageUserState 中,因为 metadata 文件是包级资源;user 维度主要通过查询时的 installed/visibility 过滤体现。更新包时,若新版本的 lastUpdateTime 更大,PMS 会先清除旧 path/source,再按新安装输入重新选择来源。
3. 安装时选择
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java;符号:commitPackageSettings。
if (oldPkgSetting != null
&& oldPkgSetting.getLastUpdateTime()
< pkgSetting.getLastUpdateTime()) {
pkgSetting.setAppMetadataFilePath(null);
pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
}
if (pkgSetting.getAppMetadataFilePath() == null) {
String dir = pkg.getPath();
if (pkgSetting.isSystem()) {
dir = Environment.getDataDirectoryPath()
+ "/app-metadata/" + pkg.getPackageName();
}
String path = dir + "/" + APP_METADATA_FILE_NAME;
if (request.hasAppMetadataFile()) {
pkgSetting.setAppMetadataFilePath(path);
pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_INSTALLER);
}
}系统包使用 /data/app-metadata/<package> 作为 metadata 目录;普通包使用其 code path 下的 metadata 文件名。更新清除只针对旧版本时间更早的情况;预加载 system-image metadata 已存在时不会被普通 installer 分支覆盖。
4. APK 内来源
当 feature flag aslInApkAppMetadataSource() 开启、请求没有 installer metadata,且 APK properties 含有 PROPERTY_ANDROID_SAFETY_LABEL 时,PMS 把路径写入 PackageSetting 并标记来源为 APP_METADATA_SOURCE_APK。真正提取发生在 post-install/scan 收尾,而不是 commit setter 内。
if (Flags.aslInApkAppMetadataSource()
&& pkg.getProperties().containsKey(
PROPERTY_ANDROID_SAFETY_LABEL)) {
pkgSetting.setAppMetadataFilePath(appMetadataFilePath);
pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_APK);
}这是一种“先登记目标路径,再异步/后置提取”的状态设计。路径存在不等于文件已经成功生成。
5. 后置提取
源码符号:InstallPackageHelper 的 extractAppMetadataFromApk 调用。
安装成功后,若来源是 APK,PMS 才调用 extractAppMetadataFromApk。提取失败会在 mLock 内把 path 清 null、source 重置为 UNKNOWN:
if (!extractAppMetadataFromApk(request.getPkg(),
pkgSetting.getAppMetadataFilePath(),
pkgSetting.isSystem())) {
synchronized (mPm.mLock) {
PackageSetting setting =
mPm.mSettings.getPackageLPr(packageName);
if (setting != null) {
setting.setAppMetadataFilePath(null)
.setAppMetadataSource(
APP_METADATA_SOURCE_UNKNOWN);
}
}
}失败不会保留一个指向不存在文件的 APK source,也不会让普通 package install 事务倒滚;metadata 是附加资源,包本身仍可安装成功。
6. 系统镜像来源
PMS 在 first boot 或 OTA upgrade 时读取 SystemConfig.getAppMetadataFilePaths()。路径不存在时保存 null;普通 package 和 disabled system package 都会被处理,并在 flag 开启时标记 SYSTEM_IMAGE。
File file = new File(path);
if (!file.exists()) {
path = null;
}
pkgSetting.setAppMetadataFilePath(path);
pkgSetting.setAppMetadataSource(
PackageManager.APP_METADATA_SOURCE_SYSTEM_IMAGE);系统 image metadata 可能在 disabled system package 上保存,供更新回退重新注册 system 版本。不能只检查当前 mPackages 中的 active package。
7. Binder 查询
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java;符号:getAppMetadataFd、getAppMetadataSource。
@EnforcePermission(Manifest.permission.GET_APP_METADATA)
public ParcelFileDescriptor getAppMetadataFd(
String packageName, int userId) {
final Computer snapshot = snapshotComputer();
PackageStateInternal ps = snapshot
.getPackageStateForInstalledAndFiltered(
packageName, Binder.getCallingUid(), userId);
if (ps == null) {
throw new ParcelableException(
new PackageManager.NameNotFoundException(packageName));
}
String filePath = ps.getAppMetadataFilePath();
if (filePath == null) {
return null;
}
try {
return ParcelFileDescriptor.open(
new File(filePath), MODE_READ_ONLY);
} catch (FileNotFoundException e) {
return null;
}
}两个查询都要求 GET_APP_METADATA,并使用 calling UID/user 的 installed/visibility 过滤。包可见但文件缺失时,FD 查询返回 null;包不可见或不存在时抛 NameNotFoundException 包装异常。
8. 验证状态
ComputerEngine.getPackageInfoInternal 在 verificationService() flag 开启时读取 DeveloperVerificationStatusInternal.isAppMetadataVerified(),把结果写入 PackageInfo。这是“metadata 是否通过开发者验证”的状态,不是 metadata source,也不等于 FD 一定可读。
if (Flags.verificationService()) {
var status = ps.getDeveloperVerificationStatusInternal();
if (status != null) {
packageInfo.setIsAppMetadataVerified(
status.isAppMetadataVerified());
}
}因此查询需要分成三件事:source 告诉文件来自哪里,FD 告诉文件当前是否存在且可读,verified 告诉验证服务的结果。
9. 更新与清理
更新包时,旧 metadata path/source 会在新版本 commit 中清除;若新请求有 installer metadata,则写入新文件路径。安装失败或旧 code path 清理不会自动删除任意外部 metadata 文件,具体删除由安装器/RemovePackageHelper 的 code path cleanup 和 metadata owner 决定。
系统包回退时,disabled system package 的 system-image metadata 可以重新进入 PackageSetting;若 path 不存在,source 仍可能是 SYSTEM_IMAGE 但 FD 返回 null,调用者应同时检查 path 与 source。
10. 故障定位
| 现象 | 检查点 | 解释 |
|---|---|---|
| source 为 UNKNOWN | installer file、APK safety label、feature flag | 未选择来源或 APK 提取失败 |
| source 为 APK 但 FD 为 null | path、post-install extraction、文件存在性 | source 是状态,不能证明文件存在 |
| 无权限读取 metadata | GET_APP_METADATA | 不是普通 PackageManager 查询权限 |
| system 包 metadata 丢失 | first boot/OTA SystemConfig、disabled setting | system-image path 可能挂在 disabled package |
| 更新后仍读旧 metadata | lastUpdateTime 清除分支、旧文件 cleanup | 新旧 path/source 与物理文件是不同阶段 |
| verified=false 但 source 正常 | DeveloperVerificationStatusInternal | 验证结果与来源/FD 独立 |
11. 源码练习
- 对比 installer、APK、system image 三种 metadata source,列出各自路径、写入阶段和失败后的状态。
- 构造 APK source 提取失败,追踪 path/source 如何被重置,并说明为什么安装本身不必失败。
- 对比
getAppMetadataFd、getAppMetadataSource和PackageInfo.isAppMetadataVerified,解释三者分别回答什么问题。 - 给定 OTA 后 active system package 没有 metadata、disabled system package 有 metadata,沿 SystemConfig 和 PackageSetting 判断查询哪个对象。
