14. 包信息查询:getPackageInfo、getInstalledPackages、queryIntentActivities 的内部实现

说实话,做Android开发这么多年,我调过无数次 getPackageInfoqueryIntentActivities。但直到我自己去啃PackageManagerService源码时,才真正明白这些API背后到底干了多少活。今天我就带你一层层剥开它们。

14.1 三个API的“面子”与“里子”

先看一张总览图,理清这三个API在系统里的位置。

包信息查询核心流程 应用层 API 调用 getPackageInfo getInstalledPackages queryIntentActivities PackageManagerService 解析 AndroidManifest 查询 Intent 过滤器 权限检查 缓存管理 底层数据源 packages.xml packages.list /data/system/

你看,所有查询最终都落到PackageManagerService(PMS)头上。但每个API的侧重点不同,内部走的路也略有差异。

14.2 getPackageInfo:查一个包的“户口本”

这个API最常用。你传一个包名,它返回一个 PackageInfo 对象。里面装着版本号、签名、权限、Activity列表等等。

内部调用链大致是这样的:

// 应用层
context.getPackageManager().getPackageInfo(packageName, flags)

// 进入系统服务
→ ActivityThread.getPackageManager().getPackageInfo(packageName, flags, userId)

// Binder 跨进程
→ PackageManagerService.getPackageInfo(packageName, flags, userId)

// 核心实现
→ PMS.getPackageInfoInternal(packageName, flags, filterCallingUid, userId)

getPackageInfoInternal 里,PMS会先查缓存。如果缓存命中,直接返回。否则从 mPackages 这个 ArrayMap<String, PackageParser.Package> 里找。

关键点: mPackages 是PMS启动时扫描所有APK后构建的内存数据结构。它不会每次查询都去读磁盘,否则性能就崩了。

我记得有一次排查问题,发现 getPackageInfo 在某些机型上返回的 versionCode 是0。后来定位到是PMS在解析APK时,AndroidManifest.xmlversionCode 属性写成了 versioncode(小写)。系统解析器是大小写敏感的,直接忽略了。嗯,这种坑其实挺常见的。

14.3 getInstalledPackages:拉一份“全量名单”

这个API返回所有已安装应用的 PackageInfo 列表。你可能会想,它不就是循环调用 getPackageInfo 吗?

其实不是。内部实现更聪明。

// 伪代码逻辑
public List<PackageInfo> getInstalledPackages(int flags, int userId) {
    // 1. 权限检查:调用方必须有 QUERY_ALL_PACKAGES 或 INSTALL_PACKAGES
    // 2. 从 mPackages 中遍历所有 PackageParser.Package
    // 3. 对每个包调用 generatePackageInfo() 生成 PackageInfo
    // 4. 过滤掉不可见的包(根据调用方uid和包可见性规则)
    // 5. 返回列表
}

这里有个性能陷阱。如果你传了 GET_META_DATAGET_SIGNATURES 等flag,每个包都要额外解析对应的数据块。我见过有人直接传 GET_META_DATA | GET_SIGNATURES | GET_ACTIVITIES 去拉全量列表,结果在低端机上直接卡了几秒钟。

避坑指南: 我曾经在一个Launcher项目里,每次启动都调用 getInstalledPackages(GET_ACTIVITIES),导致桌面启动慢。后来改成只拉包名列表,等用户点击时再查详情,流畅度明显提升。

14.4 queryIntentActivities:隐式Intent的“匹配引擎”

这个API是隐式Intent的核心。你给一个Intent,它返回所有能响应的Activity信息。

内部实现比前两个复杂得多。因为它要做 Intent 匹配

// 简化后的匹配流程
public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) {
    // 1. 解析Intent中的action、category、data等
    // 2. 遍历所有已安装的Activity组件
    // 3. 对每个Activity的IntentFilter进行匹配:
    //    - action 必须匹配(如果Intent指定了action)
    //    - category 必须匹配(默认包含 CATEGORY_DEFAULT)
    //    - data 的scheme、host、port、path、mimeType 必须匹配
    // 4. 按优先级排序
    // 5. 去重(同一个包可能有多个Activity匹配)
    // 6. 返回 ResolveInfo 列表
}

匹配逻辑在 IntentResolver 类里实现。它内部维护了多组哈希表,比如 mActionToFiltermSchemeToFilter,用来加速匹配。你想想看,如果每次查询都遍历所有Activity,那手机得卡成什么样。

个人经验: 我调试过一个bug,某个第三方应用用 queryIntentActivities 查不到我们预置的Activity。最后发现是因为我们的IntentFilter里写了 android:priority="0",而系统默认优先级是0,但某些厂商的PMS实现里,优先级为0的Filter会被跳过。这种兼容性问题,只能靠多测试来发现。

14.5 三个API的对比总结

API 查询维度 性能消耗 典型场景
getPackageInfo 单个包名 低(缓存命中率高) 获取版本号、签名、权限
getInstalledPackages 全量包 中(遍历+生成对象) 应用列表、批量操作
queryIntentActivities Intent匹配 高(需要遍历+匹配) 打开文件、分享、自定义协议

说白了,这三个API各有各的脾气。用对了地方,性能不是问题。用错了,就是卡顿和ANR的导火索。

14.6 一些底层细节

最后聊几个你可能没注意到的点。

  • 权限检查:从Android 11开始,getInstalledPackages 默认只返回当前用户可见的应用。如果你想查全量,必须声明 QUERY_ALL_PACKAGES 权限。这个权限在Google Play上审核很严。
  • 跨用户查询:PMS内部会通过 userId 隔离数据。你查不到其他用户安装的应用,除非是系统应用。
  • 缓存失效:当有应用安装、卸载、更新时,PMS会清空相关缓存。所以 getPackageInfo 在安装广播后第一次调用,可能会稍微慢一点。

我记得有一次,一个同事在 onPackageAdded 广播里立刻调用 getPackageInfo,结果返回null。原因就是PMS还没完成APK的完整解析,缓存还没更新。后来我们加了一个延迟重试机制才解决。

嗯,关于包信息查询的内部实现,今天就聊这么多。这些API看起来简单,但背后的设计思路——缓存、权限、跨进程、匹配算法——都是值得反复品味的。


公众号:蓝海资料掘金营,微信deep3321