Skip to content

PackageProperty 索引

追踪 Android 17 property 从解析结果到五类索引、可见性查询和卸载清理的源码链路。

基于android-17.0.0_r1
AndroidPMSPackagePropertyManifestPackageManager

PackageProperty 索引 ​

本文面向已经读过 meta-data 与 property 解析、PackageStateMutator 事务提交 和 安装后注册 的读者。本文不重新讲 <property> 的 XML 值类型,而是继续追踪 property 离开 AndroidPackage 后如何进入 PMS 的 PackageProperty 索引、如何按调用者过滤查询结果,以及包删除时如何避免索引残留。

1. 五张索引 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageProperty.java。

PackageProperty 不把所有 property 放在一张表中,而是按声明位置维护五张 map:application、activity、provider、receiver、service。每张表都采用 property name -> package name -> list<Property> 的三级结构。

java
private ArrayMap<String, ArrayMap<String, ArrayList<Property>>>
        mApplicationProperties;
private ArrayMap<String, ArrayMap<String, ArrayList<Property>>>
        mActivityProperties;
private ArrayMap<String, ArrayMap<String, ArrayList<Property>>>
        mProviderProperties;
private ArrayMap<String, ArrayMap<String, ArrayList<Property>>>
        mReceiverProperties;
private ArrayMap<String, ArrayMap<String, ArrayList<Property>>>
        mServiceProperties;

这个结构同时支持两类查询:给定 package + class 精确查找一个组件 property;给定 property name + component type 枚举所有包的定义。它还保留同一包同名 property 的多个 Property 对象,以应对不同组件分别声明同名属性。

2. 安装注册 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java;符号:addAllPackageProperties。

包扫描并提交到 mPackages 后,PMS 调用 mPackageProperty.addAllProperties(pkg)。这一步发生在包已经成为可查询对象之后;解析阶段的 property 仍只是 AndroidPackage 内的 Map,尚未进入全局索引。

java
void addAllPackageProperties(@NonNull AndroidPackage pkg) {
    mPackageProperty.addAllProperties(pkg);
}

PackageProperty.addAllProperties 分别读取 pkg.getProperties() 和四类主组件列表。组件没有 property 时跳过,不为每个组件预分配空 map;大量无 property 的组件不会导致索引按组件数量增长。

3. 插入顺序 ​

源码符号:addProperties。

java
for (Property property : properties.values()) {
    String propertyName = property.getName();
    String packageName = property.getPackageName();
    ArrayMap<String, ArrayList<Property>> propertyMap =
            returnCollection.get(propertyName);
    if (propertyMap == null) {
        propertyMap = new ArrayMap<>();
        returnCollection.put(propertyName, propertyMap);
    }
    ArrayList<Property> packageProperties =
            propertyMap.get(packageName);
    if (packageProperties == null) {
        packageProperties = new ArrayList<>(properties.size());
        propertyMap.put(packageName, packageProperties);
    }
    packageProperties.add(property);
}

同一 package 的同名 property 不会在这里被覆盖,而是追加到列表;精确 getProperty 从列表尾部反向查找匹配 className。application property 则返回该 package 列表的第一个对象,因为 application owner 没有 className 维度。

4. 精确读取 ​

getProperty(propertyName, packageName, className) 根据 className 是否为空选择 application 或 component 查询。component 查询依次检查 activity、provider、receiver、service 四张表;没有单独的“component type 参数”,调用者只需给出类名。

java
private Property getComponentProperty(String propertyName,
        String packageName, String className) {
    Property property = null;
    if (mActivityProperties != null) {
        property = getProperty(propertyName, packageName,
                className, mActivityProperties);
    }
    if (property == null && mProviderProperties != null) {
        property = getProperty(propertyName, packageName,
                className, mProviderProperties);
    }
    if (property == null && mReceiverProperties != null) {
        property = getProperty(propertyName, packageName,
                className, mReceiverProperties);
    }
    if (property == null && mServiceProperties != null) {
        property = getProperty(propertyName, packageName,
                className, mServiceProperties);
    }
    return property;
}

如果同一个 className 在不可能的多个组件类别中重复声明,返回顺序由这段代码决定;正常 manifest 解析会保证 class 属于一个具体组件。

5. 批量查询 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java;符号:IPackageManagerImpl.queryProperty。

java
public ParceledListSlice<PackageManager.Property> queryProperty(
        String propertyName, int componentType) {
    Objects.requireNonNull(propertyName);
    final int callingUid = Binder.getCallingUid();
    final int callingUserId = UserHandle.getCallingUserId();
    final Computer snapshot = snapshotComputer();
    final List<Property> result = mPackageProperty.queryProperty(
            propertyName, componentType,
            packageName -> snapshot.getPackageStateForInstalledAndFiltered(
                    packageName, callingUid, callingUserId) == null);
    return result == null ? ParceledListSlice.emptyList()
            : new ParceledListSlice<>(result);
}

PMS 把 visibility 过滤器传给 PackageProperty,后者只负责索引遍历。过滤条件包含 package 存在、对 user 安装和 calling UID 可见;因此同一个 property name 对 system UID 和普通应用可能返回不同列表。

componentType 非五种合法位置时,PackageProperty.queryProperty 返回 null,PMS 将其转换为空 ParceledListSlice。未知类型不是异常,也不会扫描所有五张表。

6. 删除清理 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/RemovePackageHelper.java;符号:cleanPackageDataStructuresLILPw。

java
mPm.mComponentResolver.removeAllComponents(pkg, chatty);
mPermissionManager.onPackageRemoved(pkg);
mPm.getPackageProperty().removeAllProperties(pkg);

移除 mPackages 中的 AndroidPackage 后,PMS 必须同步删除该包在五张 property map 中的所有对象。removeProperties 还会清理空的 package map 和空的 property-name map,避免已删除包的名称继续占用索引。

如果只删除 ComponentResolver 而忘记 removeAllProperties,后续 queryProperty 可能返回一个已经不存在的包;如果只清空 PackageSetting 而不移除 property,则可见性过滤可能暂时隐藏问题,直到包重新安装或索引重建。

7. 更新替换 ​

更新包时,InstallPackageHelper 在 commit 前移除旧 AndroidPackage 的组件和 property,再把新包通过 addAllPackageProperties 加入。因为 map value 保留 Property 所属 package/class,旧对象不能继续留在列表中,否则同名 property 查询会同时返回旧/新定义。

property 索引不是 per-user 状态;同一 APK 的 application/activity property 对所有 user 共享。user 差异只在 queryProperty 的 visibility/installed 过滤阶段产生,不会复制五张全局 map。

8. 标签边界 ​

<meta-data> 在解析后主要进入组件 Bundle,由 GET_META_DATA 路径返回;<property> 保留强类型 PackageManager.Property,进入 PackageProperty 索引。两者可以同名,但读取 API、重复项语义和消费者不同。

因此 queryProperty 返回空不代表 GET_META_DATA 也为空;反过来,Bundle 中存在 meta-data 也不代表 PackageProperty 有对应索引。

9. 调试路径 ​

现象检查点解释
queryProperty 为空componentType、property name、visibility未知 type 直接返回空;包不可见也被过滤
更新后出现重复结果旧包 removeAllProperties 是否执行新旧 property 对象不能共存
卸载后仍能查到 propertyRemovePackageHelper.cleanPackageDataStructuresLILPw必须清理五张索引
application property 找不到className 是否错误传入className 非空会走 component 查询
component property 找不到类别和 className、解析结果精确查找依赖 Property.getClassName()
普通应用与 system 查询不同getPackageStateForInstalledAndFiltered结果按调用 UID/user 过滤

10. 源码练习 ​

  1. 构造一个 application property 和一个同名 Activity property,分别用 getProperty 与 queryProperty 判断返回对象集合。
  2. 追踪更新包时旧 property 的 remove、新 property 的 add,解释为什么索引 value 不是简单覆盖一个 package key。
  3. 给定 property name 正确但 componentType 为未知值,定位 PackageProperty.queryProperty 返回 null 到 PMS 空 slice 的转换。
  4. 对比 system UID 与普通 UID 查询同名 property,沿 snapshot visibility 过滤判断两者结果为何不同。