本文介绍APK的安装流程。
一、安装流程图
APK安装流程,总体可以下图流程,用ProcessOn画的,凑合看:
从上图我们可以看到apk安装到最后都会调用到这个flow:
PMS.scanPackageTracedLI => PMS.scanPackageLI => PMS.scanPackageDirtyLI
关于这个flow,之前的博客有详细介绍过,本文不再展开 : https://maoao530.github.io/2017/01/10/packagemanager/
后续的博文会根据这张图展开说明。
二、APK文件结构
APK(Android Package),可以看做是一个zip压缩包,可以通过解压缩工具解开,其文件结构如下:
目录 or 文件 | 描述 |
---|---|
assert | 存放的原生资源文件,通过AssetManager类访问 |
lib | native库文件 |
META-INF | 存放签名信息,用来保证APK包的完整性和系统的安全。系统安装APK时,应用管理器会按照对应算法对包里文件做校验,如果校验结果与META-INF中内容不一致,则不会安装这个APK。 |
res | 种资源文件系统会在R.java里面自动生成该资源文件的ID,所以访问这种资源文件比较简单,通过R.XXX.ID即可 |
AndroidManifest.xml | 每个应用都必须定义和包含,描述应用的名字、版本权限、引用的库文件等信息。apk中的AndroidManifest.xml经过压缩,可以通过AXMLPrinter2工具解开。 |
classes.dex | 是JAVA源码编译后生成的JAVA字节码文件。但Android使用的dalvik虚拟机与标准的JAVA虚拟机不兼容,dex文件与class文件相比,不论是文件结构还是opcode都不一样。 |
resources.arsc | 编译后的二进制资源文件。 |
三、APK安装方法
APK有下面4种安装方法:
方法 | 描述 |
---|---|
开机过程中安装 | 开机时完成,没有安装界面,如系统应用、其它预置应用 |
adb工具安装 | 没有安装界面,adb install/push xxxx.apk |
第三方应用安装 | 通过packageinstaller.apk进行安装,有安装界面,如打开文件管理器并点击sdk卡里APK文件 |
网络下载应用安装 | 通过google market应用完成,没有安装界面 |
简单说明下apk安装的基本过程:
- 拷贝目标apk到指定文件目录
- 调用scanPackageLI为apk文件在系统中注册信息
四、应用程序安装过程
上述几种安装方法最终都通过PackageManagerService.scanPackageLI完成,总结起来大致有以下三种方式:
adb push:
PackageManagerService的内部类AppDirObserver实现了监听app目录的功能,当把某个APK文件放到app目录下面时,PMS会收到ADD_EVENTS事件
frameworks\base\services\java\com\android\server\pm\PackageManagerService.javaadb install:
安装入口函数为Pm.runInstall
frameworks\base\cmds\pm\src\com\android\commands\pm\Pm.java网络下载应用安装和第三方应用安装:
安装入口函数为ApplicationPackageManager.installPackage
frameworks\base\core\java\android\app\ApplicationPackageManager.java
接下来我们来分别详细说明这些安装流程:
五、adb push
Android 4.4平台,PackageManagerService的内部类AppDirObserver实现了监听app目录的功能,当把某个APK文件放到app目录下面时,PMS会收到ADD_EVENTS事件。
如果是添加事件,则调用scanPackageLI,并使用updatePermissionsLPw授权;如果是删除事件则调用removePackageLI移除该apk的相关信息。最后都要调用writeLPr重新保存相关信息到packages.xml。
关于AppDirObserver具体如何监听的,可以查看:AppDirObserver
不过我在android 7.0 sdk里面没有看到这个类,难道7.0把这个功能砍了?手头没有7.0平台,不好验证。
我猜测现在通过adb push apk到data/app或者system/app的apk,如果这个监听的功能砍了,那么应该是会通过reboot重启系统,走PMS.main流程,scanDir–>scanPackageLI去安装apk。
以上待填坑。
六、adb install
adb install 的安装方式,会调用system/core/adb/commandline.cpp中的adb_commandline函数:
这个过程会把apk文件copy到data/local/tmp/目录下,然后向shell服务发送pm命令安装apk,最后调用Pm.runInstall()
方法来安装apk。
6.1 pm.runInstall
frameworks\base\cmds\pm\src\com\android\commands\pm\Pm.java
|
|
从上面的代码来看,runInstall主要进行了三件事,即创建session、对session进行写操作,最后提交session。
6.1.1 doCreateSession
实际调用的是PackageInstallerService的createSession,这个过程主要是为APK安装做好准备工作,例如权限检查、目的临时文件的创建等, 最终创建出PackageInstallerSession对象。PackageInstallerSession可以看做是”安装APK”这个请求的封装,其中包含了处理这个请求需要的一些信息。
实际上PackageInstallerSession不仅是分装请求的对象,其自身还是个服务端。
6.1.2 doWriteSession
通过PackageInstallerSession将/data/local/tmp的apk拷贝到终端目录内。
6.1.3 doCommitSession
doWriteSession结束后,如果没有出现任何错误,那么APK源文件已经copy到目的地址了,doCommitSession最终会调用到PMS.installStage来安装apk,调用流程如下:
PackageInstallerSession.commit ==> commitLocked(); ==> PMS.installStage()
PMS.installStage()会调用sendMessage将”INIT_COPY”发送给PackageHandler:
|
|
PackageHandler用于处理apk的安装请求等消息,后面分析。
七、ApplicationPackageManager
网络下载应用安装或者通过第三方应用安装,最终都会通过ApplicationPackageManager.installPackage来安装:
|
|
PMS.installPackageAsUser调用sendMessage将”INIT_COPY”发送给PackageHandler:
|
|
PackageHandler用于处理apk的安装请求等消息,后面分析。
八、PackageHanlder
- PMS.installStage()会调用sendMessage将”INIT_COPY”发送给PackageHandler
- PMS.installPackageAsUser调用sendMessage将”INIT_COPY”发送给PackageHandler
8.1 INIT_COPY
PackageHandler用于处理apk的安装请求等消息,在PMS构造函数中有初始化。实际处理消息的函数为doHandleMessage,我们来看看INIT_COPY的处理流程:
|
|
INIT_COPY主要是将新的请求加入到mPendingIntalls中,等待MCS_BOUND阶段处理。
8.2 MCS_BOUND
INIT_COPY最后会发送MCS_BOUND消息触发接下来的流程,MCS_BOUND对应的处理流程同样定义于doHandleMessage中:
|
|
这一段代码比较好理解:
- 如果mPendingInstalls不为空,调用
InstallParams.startCopy
函数处理安装请求。 - 接着如果mPendingInstalls不为空,发送MCS_BOUND继续处理下一个,直到队列为空。
- 如果队列为空,则等待一段时间后,发送MCS_UNBIND消息断开与安装服务的绑定。
九、startCopy
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
InstallParams继承HandlerParams,实际调用的是HandlerParams.startCopy:
|
|
PMS将先后调用handleStartCopy和handleReturnCode来完成主要的工作。
9.1 handleStartCopy
handleStartCopy函数在HandleParams抽象类定义,在其子类InstallParams来实现,我们看看与实际安装相关的handleStartCopy函数:
|
|
InstallParams$handleStartCopy()主要功能是获取安装位置信息以及复制apk到指定位置。抽象类InstallArgs中的copyApk负责复制APK文件,具体实现在子类FileInstallArgs和SdInstallArgs里面。
9.2 handleReturnCode
InstallParams$handleReturnCode()中,调用processPendingInstall方法处理安装:
|
|
9.3 processPendingInstall
主要的安装流程都在这个方法里面: PMS.processPendingInstall
|
|
安装过程放在一个线程里面,处理流程是预安装-安装-安装收尾-发送 POST_INSTALL消息:
- 预安装:检查当前安装包的状态以及确保SDCARD的挂载,并返回状态信息。在安装前确保安装环境的可靠。
- 安装:对mInstallLock加锁,表明同时只能有一个安装包进行安装;然后调用installPackageTracedLI完成具体安装操作。
- 安装收尾: 检查状态,如果安装不成功,删除掉相关目录文件。
- 发送POST_INSTALL消息:该消息由PackageHandler接收。POST_INSTALL的主要工作其实还是通过广播、回调接口通知系统中的其它组件,有新的Pacakge安装或发生了改变。
从上面我们可以知道,具体安装apk的函数是PMS.installPackageTracedLI
。
十、installPackageTracedLI
PMS.installPackageTracedLI函数:
|
|
十一、installPackageLI
继续PMS.installPackageLI:
|
|
这个函数过程比较长,主要做了几件事:
- PackageParser$parsePackage,主要是解析APK的AndroidManifest.xml,将每个标签对应的信息添加到Package的相关列表中,如将
下的 信息添加到Package的activities列表等。 - 加载apk证书,获取签名信息
- 检查目前安装的APK是否在系统中已存在:
- 已存在,则调用
replacePackageLIF
进行替换安装。 - 不存在,否则调用
installNewPackageLIF
进行安装。
- 已存在,则调用
11.1 replacePackageLIF
如果需要替换的是系统APP,则调用Settings$disableSystemPackageLPw来disable旧的APK;如果替换的是非系统APP,则调用deletePackageLI删除旧的APK。
因为这个过程实在太差,没有必要贴出来一一分析,我来简化一下flow,有兴趣的读者可以深入跟进:
|
|
不管是更新系统还是非系统apk,都会先清除之前的packages信息,然后通过scanPackageTracedLI去安装apk,安装完后更新permissions和setting,最后通过writeLPr更新packages.xml。
关于scanPackageTracedLI和Settings.writeLPr();我有在上一篇blog讲过,可以回去看看。
11.2 installNewPackageLIF
PMS.installNewPackageLIF用于安装新的apk:
|
|
installNewPackageLIF会调用scanPackageTracedLI去安装apk,最终会调用scanPackageLI->scanPackageDirtyLI实际去安装apk。
由于之前有描述过,便不再叙述。