本篇文章只是作为指南引导去看PkMS,不会贴很多代码分析,要对着源码进行看,更多是基于方法分析实现的逻辑,另外就是代码是基于Android 11,与Android 10之前代码有比较大的差别。
本文的内容
- 解析什么?
- 解析之后得到了什么?
- 解析过程分析
解析什么?
答案是AndroidManifest.xml,它相当于package的配置文件,里面存储了一个Android应用运行所需要的所有配置信息,PkMS目的就是将配置AndroidManifest.xml解析出来,供应用以及服务(AMS启动四大组件会从PkMS找信息)使用,PkMS设计的目的无非就是提供对应用的增删改查的操作。
解析之后的结构
这里列出了部分解析之后的属性信息,可以看到大部分属性其实都可以和AndroidManifest.xml当中的标签对应上,虽然结构是如此,但是在PackageManagerService中使用最主要还是用
AndroidPackage进行操作属性,这里没有列出AndroidPackage的方法,一方面是里面的方法都比较直观,另一方面就是方法实在是太多了,列出来容易忽视掉重点(其实就是懒)。
另外就是解析过程中可能会有很多错误或者异常情况,所以AOSP开发者用了一个ParseInput来专门处理错误,下面解析过程中返回值的UML类图
里面
ResultType泛型就是ParsingPackage或者ParsedPackage。
为什么要设计ParsedPackage和ParsingPackage
因为AndroidManifest.xml里面的配置都是不可变的,所以在解析完成为了防止修改与配置相关的属性设计了ParsedPackage,这个接口只有读配置的操作。
解析过程
UML图
什么时候开始解析
这里UML画的是开机解析的过程,很容易想到就是当应用安装(不管是adb还是PackageInstaller)的时候会解析。
解析类的初始化
对应UML时序图中的<init>()方法,这部分主要是对解析必要的对象进行初始化
初始化ParallelPackageParser
PackageManagerService使用的是线程池对所有应用进行解析,准确的说是生产者/消费者模式进行解析应用,这样做的原因是应用的数量可能会有上百个。ParallelPackageParser初始化可以看做是线程池的初始化
初始化PackageParser2
Android 11之前用的是PackageParser,PackageParser2一共做了下面几件事
- 初始化
PackageCacher,cache主要是用在开机的过程当中,使用cache缓存解析结果,可以加快开机的速度,是用空间换时间的一种方式 - 初始化
ParsingPackageUtils,它是解析的主要工具类之一 - 初始化ParseInput.Callback,这部分的代码暂时没有理解透
- 初始化ThreadLocal,为后面每个线程副本做准备
初始化ParsingPackageUtils
主要是说一下第四个参数callback,它的类型是ParsingPackageUtils.Callback,一个重要的实现类是在PackageParser2当中,实现了startParsingPackage()方法,用于创建PackageImpl对象。ParsingPackageUtils.Callback对象的创建是在PackageManagerService.<init>()当中
开始解析过程
接下来是按照UML时序图中的由上至下,由左至右的方式分析每个方法的逻辑
ParallelPackageParser.submit()
逻辑比较简单直接就是调用PackageParser2.parsePackage()方法
PackageParser2.parsePackage()
- 看是否支持使用caches,如果支持且获取到了package信息,则直接返回。一般开机的时候解析是支持cache,安装应用的时候是不支持,至于理由,因为关机的时候基本不会安装应用,所以要cache,而安装应用都是安装新应用,所以不必支持cache
- 获取ParseInput,这个是直接从ThreadLocal中获取,每一个线程都是单独的一份副本,用于做输出错误信息
- 调用
ParsingPackageUtils.parsePackage()继续解析AndroidManifest.xml,解析成功后将 - 将解析结果的
ParsingPackage转为ParsedPackage使用 - 如果支持cache,则将解析结果缓存到cachedir当中
ParsingPackageUtils.parsePackage()
Android 5.0之前都是调用parseMonolithicPackage(),之后默认都是默认使用目录的形式,调用parseClusterPackage(),接下来都是通过parseClusterPackage()进行解析
ParsingPackageUtils.parseClusterPackage()
- 调用
ApkLiteParseUtils.parseClusterPackageLite进行轻度解析,主要是对于顶层的manifest、uses-sdk、application等标签进行解析,这一步主要的作用其实是判断当前是否处于onlyCoreApp模式,一般工厂模式会有,这种模式下不支持第三方应用,只有加了coreApp标签的应用才会被安装,一般都不会用到 - 创建
AssetManager,其功能是用于获取apk当中的资源文件。 - 调用
parseBaseApk()解析base apk,这里要区分一下base apk和split apk,split apk是用于支持App bundle,这里主要关注base apk的解析。 - 调用
parseSplitApk()解析split apk,
ApkLiteParseUtils.parseClusterPackageLite()
这是个静态方法,会对于目录内所有的.apk文件进行解(base apk和split apk)
- 递归调用
parseClusterPackageLite()对子目录进行解析 - 遍历目录内的每个apk文件调用
parseApkLite(),并获取结果 - 获取包名和版本号,并检查split和base apk是否一致,防止安装错误应用,要求base.apk和所有的split apk都需要有相同的包名和版本号
- 获取base apk,base 和 split apk中重要的区别方式是split apk必须包含split name,而base apk中的split name为null
- 打包base apk和split apk的属性
ApkLiteParseUtils.parseApkLite(ParseInput input, File apkFile, int flags)
直接调用parseApkLiteInner
ApkLiteParseUtils.parseApkLiteInner()
- 获取XmlResourceParser,并打开AndroidManifest.xml准备解析
- 判断是否需要获取签名,一般是不用,目前没看到什么地方需要获取签名的情况
- 最后调用
parseApkLite(),注意这个是重载的5个参数的方法
ApkLiteParseUtils.parseApkLite(),5个参数
- 调用
ApkLiteParseUtils.parsePackageSplitNames()获取split name,这里不进行进一步分析,因为基本就是xml解析的基本操作而已,这里base apk返回的是null - 继续解析其他标签,填充 ApkLite中的属性
ParsingPackageUtils.parseBaseApk()
对于base apk的AndroidManifest.xml中的标签全面的解析,并返回ParsingPackage。
- 调用
ApkLiteParseUtils.parsePackageSplitNames()获取base apk的split name, 它里面的值应该为null - 判断split name是否为null,不为null会报错
- 调用
Callback.startParsingPackage(),创建一个空的解析结果 - 调用
parseBaseApkTags(),解析AndroidManifest.xml中的标签,并将结果放到ParsingPackage中
ApkLiteParseUtils.parsePackageSplitNames()
不进一步分析,xml的解析操作
Callback.startParsingPackage()
它的实现是在PackageParser2.Callback抽象类中,通过PackageImpl.forParsing()创建PackageImpl对象
ParsingPackageUtils.parseBaseApkTags()
不做进一步分析,解析xml的tag,并填充到ParsingPackage当中