3. 核心数据结构(上):Package、PackageParser、PackageSetting、PackageUserState 源码级解析

各位同学,今天我们来啃几块硬骨头。PackageManagerService(简称PKMS)能管理成千上万个App,靠的就是一套精心设计的数据结构。说白了,这些结构就是PKMS的“骨架”和“血液”。我个人习惯,在分析系统服务时,先看数据,再看流程。数据搞明白了,流程就是顺水推舟的事。

这一节,我们聚焦四个核心类:PackagePackageParserPackageSettingPackageUserState。它们分别负责“解析”、“存储”、“运行时状态”。嗯,咱们一个一个来。

3.1 PackageParser:从APK到内存对象的“翻译官”

你想想看,一个APK文件本质上就是个zip包。系统怎么知道它里面有哪些组件、权限、版本号?全靠PackageParser来解析。它负责读取AndroidManifest.xml,把二进制XML转换成我们熟悉的Java对象。

核心入口是parsePackage()方法。它会先解析Manifest的根节点,然后遍历<application><activity><service>等子节点。每解析一个组件,就会生成一个对应的内部类对象,比如ActivityServiceProvider

核心要点:PackageParser解析的结果是一个Package对象。这个对象包含了APK的所有静态信息,比如包名、版本号、安装路径、所有组件列表等。但它不包含任何运行时状态。

我曾经在适配Android 11时踩过一个坑:PackageParser在解析<queries>标签时,如果格式不对会直接抛异常,导致整个APK安装失败。所以,写插件化框架的同学,一定要保证Manifest的合法性。

3.2 Package:静态信息的“集装箱”

Package类,你可以把它理解成一个“集装箱”。里面装着从Manifest里解析出来的所有静态数据。它不是一个公开API,而是系统内部使用的数据结构。

我们来看看它的核心字段:

字段名 类型 说明
packageName String 应用的包名,唯一标识
versionCode int 版本号,用于升级判断
versionName String 版本名,展示给用户看
activities ArrayList<Activity> 所有Activity组件的列表
services ArrayList<Service> 所有Service组件的列表
providers ArrayList<Provider> 所有ContentProvider组件的列表
receivers ArrayList<Activity> 所有BroadcastReceiver组件的列表
requestedPermissions ArrayList<String> 应用声明的权限列表
applicationInfo ApplicationInfo 应用级别的信息,如label、icon、theme等

注意,Package对象里的组件都是PackageParser的内部类。它们和android.content.pm包下的ActivityInfoServiceInfo等是映射关系。系统在后续流程中,会把Package里的数据“摊开”,填充到对应的Info对象里。

我的小技巧:在调试安装问题时,可以dump出Package对象,看看activities列表是否为空。如果为空,说明Manifest里的Activity没被解析到,大概率是Manifest格式问题。

3.3 PackageSetting:持久化的“身份证”

PackageSetting是存储在/data/system/packages.xml里的数据映射。它记录了每个已安装应用的“身份信息”,比如安装时间、签名、权限授予状态等。系统重启后,PKMS会从XML文件里恢复这些数据,重建PackageSetting对象。

它和Package的区别是什么?说白了,Package是“活的”,每次解析APK生成;PackageSetting是“死的”,持久化在磁盘上。一个APK可以没有Package对象(比如还没解析),但一定有PackageSetting(只要安装过)。

核心字段包括:

  • name:包名,和Package.packageName对应。
  • codePath:APK文件的完整路径。
  • resourcePath:资源文件的路径,通常和codePath相同。
  • pkgFlags:应用标志,比如是系统应用还是用户应用。
  • signatures:应用的签名信息,用于校验。
  • permissionsState:权限授予状态,记录哪些权限被授予了。

避坑指南:我曾经遇到过一个问题:OTA升级后,部分应用出现“签名不一致”的错误。查了半天,发现是packages.xml里记录的签名信息和实际APK签名不匹配。原因是系统更新时,PackageSetting被错误地保留了旧签名。所以,处理系统升级时,一定要校验签名是否变更。

3.4 PackageUserState:多用户下的“分身术”

Android从4.2开始支持多用户。同一个应用,在不同用户下可以有不同的状态。比如,用户A禁用了微信,但用户B可以正常使用。这个“用户相关”的状态,就由PackageUserState来管理。

它是个轻量级的数据结构,每个用户对应一个PackageUserState对象。PKMS内部用SparseArray<PackageUserState>来存储所有用户的状态。

核心字段:

  • enabled:boolean,应用是否启用。如果被用户手动禁用,这里就是false。
  • installed:boolean,应用是否已安装。注意,卸载应用时,这个字段会变成false,但PackageSetting可能还保留着。
  • stopped:boolean,应用是否被强制停止。用户从设置里点“强制停止”后,这个字段为true。
  • hidden:boolean,应用是否被隐藏。一般用于系统应用,不想让用户看到。
  • launchCount:int,启动次数。用于统计和排序。
  • lastLaunchedTime:long,最后一次启动的时间戳。

举个例子:当用户A在设置里禁用了某个应用,PKMS会找到该用户对应的PackageUserState,把enabled设为false。下次用户A想启动这个应用时,系统会检查enabled字段,直接拦截掉。而用户B的PackageUserState不受影响。

关键逻辑:PKMS在查询应用信息时,会合并Package(静态数据)和PackageUserState(用户状态)。比如,ApplicationInfo.enabled字段,就是由PackageUserState.enabled决定的。如果用户禁用了应用,即使Manifest里声明了enabled=true,最终返回给调用方的也是禁用状态。

3.5 四者关系:一张图看懂

这四个类不是孤立的,它们共同构成了PKMS的数据基石。我画了一张图,帮你理清它们的关系:

核心数据结构关系图 PackageParser 解析APK,生成Package Package 静态信息集装箱 PackageSetting 持久化身份证 PackageUserState 多用户分身术 解析生成 持久化存储 1:N 用户状态 引用静态数据 PackageParser 解析 APK 生成 Package → Package 的静态信息持久化为 PackageSetting → 每个用户拥有独立的 PackageUserState,控制运行时状态

从图中可以看出:PackageParser是入口,它把APK解析成Package对象。Package里的静态信息,会被持久化到PackageSetting中。而PackageUserState则负责记录每个用户对应用的个性化设置。这三者配合,PKMS才能高效地管理所有应用。

3.6 实战:如何从PKMS中获取这些数据

光说不练假把式。我们来看看,在系统源码中,如何通过PKMS获取这些核心数据结构。

// 获取Package对象(内部API,非公开)
// 在PKMS内部,可以通过mPackages这个ArrayMap获取
Package pkg = mPackages.get(packageName);
if (pkg != null) {
    // 读取所有Activity
    for (PackageParser.Activity activity : pkg.activities) {
        ActivityInfo info = activity.info;
        // 处理info...
    }
}

// 获取PackageSetting对象
PackageSetting ps = mSettings.getPackageLPr(packageName);
if (ps != null) {
    // 读取签名信息
    SigningDetails signingDetails = ps.signatures;
    // 读取权限状态
    PermissionState state = ps.permissionsState;
}

// 获取PackageUserState对象
// 需要指定userId
int userId = UserHandle.myUserId();
PackageUserState userState = ps.readUserState(userId);
if (userState != null) {
    boolean isEnabled = userState.enabled;
    boolean isInstalled = userState.installed;
}

我的调试习惯:在分析安装或权限问题时,我经常在PackageManagerService.java里加日志,打印出PackageSettingpkgFlagsPackageUserStateenabled字段。这两个值能快速定位问题:是应用本身被禁用了,还是权限没授予。

好了,这一节的内容就到这里。我们深入剖析了PackageParserPackagePackageSettingPackageUserState这四个核心数据结构。记住,它们是PKMS的基石。理解了它们,你就掌握了PKMS一半的功力。


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