通过分析apk的安装过程学习下PackagemanagerService(PMS)
在分析安装过程之前,需要先了解下Android项目是如何 编译 -> 打包,生成最终的 .apk格式的安装包。引用Google的一张图:
一个完整的Android项目可能包含多个module,而从宏观上看每一个module中的内容可以分为2部分:Resource资源文件,Java或Kotlin源代码 。因此整个项目的编译打包过程也是针对这2部分来完成 。
资源文件包括项目中res目录下的各种XML文件,动画,drawable图片,音视频等。AAPT工具负责编译项目中的这些资源文件,所有资源文件会被编译处理, XML文件(drawable图片除外)会被编译成二进制文件 ,所以解压apk之后无法直接打开XML文件。但是assets和raw目录下的资源并不会被编译,会被原封不动的打包到apk压缩包中 。
资源文件编译之后的产物包括2部分:resource.arsc文件和一个R.java。前者保存的时一个资源索引表,后者定义了各个资源ID常量。这两者结合就可以在代码中找到对应的资源引用。
如下R.java文件:
可以看出,R.java中的资源ID是一个4字节的无符号整数,用16进制表示。最高的1字节表示Package ID,次高的1字节表示Type ID,最低的2字节表示Entry ID
resource.arsc相当于一个资源索引表,可以理解为一个map映射表。其中map的key就是R.java中的资源ID,而value就是其对应的资源所在的路径。实际上resource.arsc里面还有别的信息,这里就不展开了
项目中的源码首先会通过javac编译为.class字节码文件,然后这些.class文件连同依赖的三方库中的.class文件一同被dx工具优化为.dex文件。如果有分包,那么也可能会产生多个.dex文件。
实际上源码文件也包括AIDL接口文件编译之后生成的.java文件,Android项目中如果包含.aidl接口文件,这些.aidl文件会被编译成.java文件
最后使用工具APK Builder将编译之后的resource和.dex文件一起打包到apk中,实际上被打包到apk中的还有一些其他资源,比如AndroidManifest.xml清单文件和第三方库中使用的动态.so文件
apk创建好之后,还不能直接使用。需要使用工具jarsigner对其进行签名,因为Android系统不会安装没有进行签名的程序。签名之后会生成META_INF文件夹,此文件夹中保存着跟签名相关的各个文件。
PMS在安装过程中会检查apk中的签名证书的合法性,这个后面说。
常量来说,签名之后的apk应该是可以正常安装使用了,但是实际打包过程还会多一步apk优化操作 。就是使用工具zipalign对apk中的未压缩资源(图片,视频等)进行对齐操作,让资源按照4字节的边界进行对其 。这种思想同Java对象内存分布中的对齐空间非常类似,主要是为了加快资源的访问速度 。如果每个资源的开始位置都是上一个资源之后的4n字节,那么访问下一个资源就不用遍历,直接跳到4n字节处判断是不是一个新资源即可。
至此一个完整的apk安装包就创建成功,一个完整的apk解压缩之后的内容如下:
整个编译打包流程可用下图描述:
接下来看下PMS是如何将其安装到手机设备中的。
当我们点击某一个App安装包进行安装时,首先会弹出一个系统界面指示我们进行安装操作。这个界面是Android Framework中预置的一个Activity—PackageInstallerActivity.java。 当点击安装后,PackageInstallerActivity最终会将所安装的apk信息通过PackageInstallerSession传给PMS ,具体方法在commitLocked方法中,如下:
图中的mPm就是系统服务PackageManagerService。installStage方法就是正式开始apk的安装过程。
整个apk的安装过程可以分为2大步:
从installStage方法开始,代码如下:
Message发送出去之后,由PMS的内部类PackageHandler接收并处理,如下:
通过隐式Intent绑定Service,实际绑定的Service类型是DefaultContainerService类型。当绑定Service成功之后,会在onServiceConnection方法中发送一个绑定操作的Message,如下:
MCS_BOUND的Message接收者还是PackageHandler,具体如下:
mPendingInstalls是一个等待队列,里面保存所有需要安装的apk解析出来的PackageParams参数,从mPendingInstalls中取出第一个需要安装的HandlerParams对象,并调用其startCopy方法,在startCopy方法中会继续调用一个抽象方法handleStartCopy处理安装请求。通过之前的分析,我们知道HandlerParams实际类型是InstallParams类型,因此最终调用的是InstallParams的handlerStartCopy方法
这个方法是整个安装包拷贝的核心方法,如下:
解释:
如果安装位置合法,则执行③处逻辑,创建一个InstallArgs,实际上是其子类FileInstallArgs类型,然后调用其copyApk方法进行安装包的拷贝操作。
可以看出在copyApk方法中调用了doCopyApk方法,doCopyApk方法中主要做了3件事:
上图中的IMediaContainerService实际上就是在开始阶段进行连接操作的DefaultContainerService对象,其内部copyPackage方法本质上就是执行IO流操作,具体如下:
最终安装包在data/app目录下以base.apk的方式保存, 至此安装包拷贝工作已完成 。
代码拷贝结束之后,就开始进入真正的安装步骤
代码回到上述的HandlerParams中的startCopy方法:
可以看出当安装包拷贝操作结束之后,继续调用handleReturnCode方法来处理返回结果,最终调用processPendingInstall方法处理安装过程,代码具体如下:
解释:
installPackageLI是apk安装阶段的核心代码,核心代码如下:
installNewPackageLI方法是负责完成最后的apk安装过程,如下:
解释:
至此整个apk的安装过程结束,实际上安装成功之后,还会发送一个App安装成功的广播ACTION_PACKAGE_ADD。手机桌面应用注册这个广播当收到之后,就将app的启动icon显示在桌面
主要总结学习了一个Android项目从编译成apk文件,然后被安装到手机设备上的简要过程。其中编译分为2部分:资源+源码。并且生成apk之后还要进行签名,对齐等操作。apk安装也分为2步:安装包拷贝和代码装载。