1. ADB处理的流程
通过adb install命令安装指定apk时,会先将该apk上传到/data/local/tmp或者/sdcard/tmp目录下,然后调用pm脚本,通过Binder通信,调用PackageManagerService的接口安装apk。
通过PackageInstaller.apk安装指定apk时,最终也会调用PackageManagerService的接口实现apk的安装。
以上两种情况最终都会发送INIT-COPY消息,由PackageManagerService启动过程中初始化的mHandler的handleMessage方法处理该消息,开始apk的安装。
1.1. adb 拷贝流程分析
adb 安装apk支持的命令
1 | " adb install [-lrtsdg] <file>\n" |
1 | static int install_app_legacy(TransportType transport, const char* serial, int argc, const char** argv) { |
发送shell消息到终端, 使用pm install
1.2. pm执行安装流程
生成system/bin/pm
可执行文件
传入参数.执行 runInstall 函数
在runInstall函数中将shell传过来的pm命令进行解析,并添加到SessionParams的相应flag中
1 | private int runInstall() throws RemoteException { |
/data/system/install_sessions.xml
1 |
|
下一步主要涉及到doCommitSession的处理
在PackageInstallerSession的commitLocked函数中,最终调到了PKMS的installStage方法进行应用的安装
1 | final OriginInfo origin; |
2. 安装流程
2.1. 从INIT_COPY开始
首先,获取消息的保存的InstallParams对,然后判断当前是否绑定到DefaultContainerService服务,
没有绑定时,调用mHandler.connectToService方法绑定:绑定失败,直接退出;成功,将安装请求信息添加到mHandler.mPendingInstalls(存储等待安装的apk请求信息)中,稍候会处理第一个安装请求。初次绑定并成功后,会调用PackageManageService.mDefContainerConn的回调方法onServiceConnected,获取服务代理对象,并发送一个MCS-BOUND消息
1 | case INIT_COPY: { |
只有在没有绑定进行绑定时或已绑定且当前是第一个安装请求时才会发送MCS_BOUND空消息,接下来开始处理该消息。
2.2. 处理MCS_BOUND消息
首先要保存服务代理对象到PackageManagerService.mContainerService中。
如果是空消息,说明已经绑定服务,代理对象已保存,即msg.obj=null的情况
判断当前服务代理对象是否为null:如果为null,说明没有绑定服务,处理出错信息,清空所有安装请求;
1 | case MCS_BOUND: { |
已绑定,处理第一个安装请求
1 | else if (mPendingInstalls.size() > 0) { |
1 只有当安装请求队列处理到最后,后面没有安装请求时,才会发送MCS_UNBOUND消息;
2.当来了新的安装请求,而消息队列中还存在前一个安装请求的MCS_UNBOUND消息时,可能存在一种情况,就是在初始化安装时是存在绑定服务的,但是在安装过程中,由于前一个请求的MCS_UNBOUND消息,导致服务解绑,出现安装错误
判断每个apk尝试安装的次数,每个apk最多尝试安装4次,均失败,发送MCS_GIVE_UP消息,返回false;否则调用InstallParams.handleStartCopy方法进行复制安装,复制安装成功返回true,若失败,抛出并处理RemoteException,发送MCS_RECONNECTION消息,重新尝试安装。最后调用InstallParams.handleReturnCode方法处理返回结果。
1 | final boolean startCopy() { |
2.3. 调用InstallParams.handleStartCopy方法复制apk
该方法主要根据安装位置(默认installFlags既没指定onSd,也没指定onInt),复制资源文件到内部/外部存储中,或者先验证包然后在CHECK_PENDING_VERIFICATION消息处理分支实现复制和安装。
1 | // 已经缓存了文件, 根据stage的类型重新确定安装的标志 |
2.3.1. 计算推荐安装位置
如果安装位置标志既设置了内部标志又设置了外部标志,记录返回结果INSTALL_FAILED_INVALID_INSTALL_LOCATION到InstallParams.mRet中;
否则,根据指定安装位置(内部存储/外部存储),调用mContainerService.getMinimalPackageInfo方法,解析AndroidManifest.xml文按,获取包名、installLocation、package-verifier等信息,并计算存储区域的推荐安装位置,将这些信息存储到PackageInfoLite结构中:
1 | else { |
没有缓存文件时,因为要拷贝缓存文件到/data/app/***下,所以需要计算所需的空间大小,算出来后,通过installd的freecache函数清除缓存文件来释放空间.释放完之后重新计算安装位置是否满足空间要求.
此处涉及到判断空间是否满足要求的判断标准,再此重新说明一下:
2.3.2. 空间不足的判断标准
对于内部存储,是通过fitsOnInternal函数进行判定的, 判定标准时 可用空间大小 >= sizeBytes + 默认1M
对于外部存储,是通过fitsOnExternal函数进行判定, 首先判断外部存储是否挂载,再进行判断 可用空间大小 >= sizeBytes + 默认1M
1 | // sizeBytes是统计的apk的大小,如果forwardLock为true(code和resource分离?)需要计算解压后的大小.同时需要加上其关联的native库的大小 |
可用大小可以通过df命令查看
文件系统可以通过指定uid\gid保护对空间进行保护,特权进程访问到的可使用空间比较多,而普通进程看到的可用空间比较少,用以对存储进行保护
2.3.2.1. 文件系统层对存储空间做的保护
board 打开 BOARD_RESERVED_SPACE_ON 开关时
data分区 在init checkfs时会预留空间 10M大小(手机上block大小一般为512, 2560*512B=10M),预留给特定的uid gid的进程使用
tune2fs -r 2560 /dev/block/platform/../userdata
使用tune2fs -r 指定 固定值count (指定预留的 block count)
也可以通过 tune2fs -m
查看文件系统的超级块信息:
dumpe2fs -h userdata.img 或 /dev/block/../userdata
- Reserved block group size:(
resv_blocks
) 文件系统 free_block 该项必须配置,该项为系统预留值。一般通过make_ext4fs 制作img时配置。 - Reserved block count: (
ext4_r_blocks_count
) 该项为预留给 特殊进程使用的预留空间。
特权进程调用stat 看到的及可使用的可用空间为 avail = total - resv_blocks
普通进程调用stat看到的及可使用的可用空间为 avail = total - resv_blocks - ext4_r_blocks_count
resv_blocks 是 kernel 中进行指定:
1 | resv_clusters = min_t(ext4_fsblk_t, resv_clusters, 4096); |
取的分区的 2% 或 16M的最小值
2.3.3. 根据推荐安装位置处理返回结果
当安装位置不冲突时,根据根据推荐安装位置返回值指定相应的返回结果。
存在一种情况,会调用installLocationPolicy进一步处理推荐安装位置,主要是为了防止系统级应用安装到sd卡上等操作,根据推荐安装位置更新安装位置标志
正常情况下,对于安装在内部/外部存储上的应用,一般返回
PackageHelper.RECOMMEND_INSTALL_INTERNAL | PackageHelper.RECOMMEND_INSTALL_EXTERNAL
installLocationPolicy函数对应用的升级\降级情况进行了判断,如果是升级的情况下,升级的应用是system app,是不允许升级后安装到sd卡上的.
如果没有携带-r参数,且应用已经存在,即在PKMS.mPackages中能够查到,则返回应用已存在,跳出安装流程
2.3.4. 根据安装位置创建安装参数InstallArgs
根据InstallParams.mInstallFlags安装位置标志,调用createInstallArgs方法创建InstallArgs:
- 对于安装在sd卡(内和外sd卡)和FORWARD-LOCKED的apk,创建AsecInstallArgs类型
- 对于安装在手机内部存储区域的,创建FileInstallArgs类型。并存储到InstallParams.mArgs中。
- 移动应用的情况下(从内部存储移动到外部存储或反过来的情况), 创建MoveInstallArgs.
1 | final InstallArgs args = createInstallArgs(this); |
2.3.5. 验证包(GMS情况下)
在PMS启动阶段最后,如果指定了验证包,可以用它来验证后续需要验证的apk。该包被授予android.Manifest.permission.PACKAGE_VERIFICATION_AGENT
权限,它包含能处理Action为ACTION_PACKAGE_NEEDS_VERIFICATION
的Intent的组件。将该包名存储到PackageManagerService.mRequiredVerifierPackage
中,非GMS版本中一般不会指定该验证包。
当所需要安装的apk需要验证时,将验证编号和验证状态存入PackageManagerService.mPendingVerification中。
构造验证包的Intent对象
传入验证包的地址, origin对象来自于installStage
1 | final Intent verification = new Intent( |
根据该安装包解析到的验证包和receivers,查找匹配该安装包的用来验证的组件信息列表,并向这些组件发送广播;
1 | if (sufficientVerifiers != null) { |
然后根据系统指定验证包和receivers查找验证包中匹配的组件。在返回结果为成功安装的前提下,向该组件发送有序广播,该广播也包含接收目标组件广播的广播接收器,继而发送一个CHECK_PENDING_VERIFICATION的延时handler消息(默认为10s的延时消息)。
1 | final ComponentName requiredVerifierComponent = matchComponentForVerifier( |
然后将InstallParams.mArgs置为null,保证在验证完成之前不进行apk的复制。在CHECK_PENDING_VERIFICATION消息处理过程中,会发送相关广播,被PMS中定义的验证包接收,然后进行apk的复制和安装,最后会发送一个MCS_UNBOUND消息。
2.3.6. 复制apk和lib文件
1 | ret = args.copyApk(mContainerService, true); |
根据安装参数args的具体类型,决定执行FIleInstallArgs还是AsecInstallArgs的copyApk方法实现apk复制。
2.3.7. FileInstallArgs.copyApk方法
- 文件已经存在即base.apk已经拷贝完成的情况下,直接跳过拷贝流程
1 | if (origin.staged) { |
- origin.staged为空时,创建临时目录,执行文件拷贝
- 复制apk文件:调用DefaultContainerService的copyPacakge方法,先对apk文件进行解析,然后将要安装的apk文件拷贝到/data/app/vmdl<随机整数>.tmp/base.apk中;如果包含分段的apk,将各个分段apk拷贝到相应的/data/app/vmdl<随机整数>.tmp/split_<splitName[i]>.apk中;
- 复制本地库文件:调用NativeLibrariesHelper.copyNativeBinariesWithOverride方法,将apk中的lib文件拷贝到/data/app/ vmdl<随机整数>.tmp/lib/,,,中;
最终将结果返回,保存到InstallParams.mRet中,用于handleReturnCode方法对返回结果进行处理。
1 | // 创建临时目录 "vmdl" + sessionId + ".tmp" |
2.3.8. AsecInstallArgs.copyApk方法
当apk安装到sd卡(注意是安装在sd卡,并不是apk文件所在的位置),或者apk为FOWARD_LOCKED类型时调用该类的方法进行apk复制。
首先,判断是否已经复制过,如果已复制,借助MountService获取复制目录信息,更新代码/资源/库路径,跳过后续复制流程:
1 | if (origin.staged && origin.cid != null) { |
如果没复制,开始复制流程,复制流程与安装在内部流程一样,主要区别在于临时目录的路径不同(首次会先在手机中创建/mnt目录,/mnt/secure/ smdl<随机整数>.tmp存放复制的apk和lib,/mnt/secure/asec存放包名-num.asec),复制apk和库文件成功后,更新代码/资源/库路径信息(将smdl<随机整数>.tmp重命名为包名-num)。
到此,不管安装在外部还是内部存储区域的apk,复制操作完成,接下来根据复制过程的返回结果InstallParams.mRet以及InstallParams.mArgs调用InstallParams.handleReturnCode方法进行处理,实现apk的安装。
2.3.9. InstallParams.handleReturnCode安装apk
在startCopy函数中,当handleStartCopy成功执行完毕后,执行handleReturnCode函数进行apk的安装.
首先,判断InstallParams.mArgs:若为null,说明MCS不可用/进行的是包验证,不进行安装操作;否则,调用InstallParams.handleReturnCode方法进行安装:
1 | void handleReturnCode() { |
由于apk的安装是一个耗时操作,因此在processPendingInstall方法中,将安装放到消息队列中,然后在分发消息时,作为消息的回调方法,执行该Runnable,在新的线程中进行安装:
1 | mHandler.post(new Runnable() { |
首先,创建一个PackageInstalledInfo res结构,存储安装过程中的结果信息,当成功完成复制,进行安装,其中args.doPreInstall和args.doPostInstall方法只是判定当返回码不是PackageManagerINSTALL_SUCCEEDED时删除代码/资源/本地库文件,在正常安装时二者基本不做操作。真正的安装操作是在PackageManagerService.installPackageLI中完成的.
2.3.10. installPackageLI函数执行应用的安装
以安装在内部存储为例,跟一下实际安装的流程
2.3.10.1. installPackageLI 安装流程图
1 | @startuml |
分解下安装流程,分成10个步骤:
2.3.10.2. 调用parsePackage方法解析AndroidManifest.xml文件
首先调用PackageParser.parsePackage方法解析AndroidManifest.xml文件,将解析结果存入Package pkg中,这部分内容在扫描安装过程中,跳转此处介绍过,此处略去。解析失败,结束安装,在res中存储返回码和返回信息,输出log。
2.3.10.3. 存入abiOverride,TEST_ONLY
存入abiOverride,TEST_ONLY, 验证该APK是否只是用于测试,如果解析时该包只允许测试,而安装标志不允许测试,结束安装,在res中存储返回码和返回信息,输出log。
2.3.10.4. 证书验证
如果安装参数中携带的certificates(install session中已经处理了签名)不为空,则执行populateCertificates方法,直接赋值签名
Populates the correct packages fields with the given certificates.This is useful when we’ve already processed the certificates [such as during package installation through an installer session]
1 | pkg.mCertificates = certificates; |
如果安装参数中没有携带签名,或者签名的过程中在 convertToSignatures 函数中处理出错,则执行collectCertificates方法从Apk中收集签名.
该方法进行证书验证和ManifestDigest验证(验证AndroidManifest.xml,存入pkg.manifestDigest), 验证失败,结束安装,在res中存储返回码和返回信息,输出log. 系统包只校验AndroidManifest.xml文件,而其他包要校验所有文件.
1 | collectCertificatesInternal(pkg, parseFlags); |
证书验证部分在Android7.0 上新加了V2签名校验的方案,详细资料可参考这里,以及官方资料
2.3.10.4.1. V2签名认证
1 | try { |
V2签名校验通过的情况下,不会再进行V1签名的校验; 如果存在V2签名,但校验没通过,会抛出异常,校验未通过.
2.3.10.4.2. V1签名校验
关于V1签名的资料请查看这里, Android APK V1 签名原理
首先创建StrictJarFile的类实例,用来初始化
1 | jarFile = new StrictJarFile( |
├── META-INF
│ ├── ANDROID_.RSA
│ ├── ANDROID_.SF
│ └── MANIFEST.MF
.RSA 是 PKCS#7 标准格式的文件,我们只关心它所保存的以下两种数据:
a. 用
私钥
对 .SF 文件指纹
进行非对称加密
后得到的 加密数据
b. 携带公钥
以及各种身份信息的 数字证书
MANIFEST.MF、CERT.SF、CERT.RSA 如何各司其职构成了 APK 的签名:
a. 解析出 CERT.RSA 文件中的证书、公钥,解密 CERT.RSA 中的加密数据
b. 解密结果和 CERT.SF 的指纹进行对比,保证 CERT.SF 没有被篡改
c. 而 CERT.SF 中的内容再和 MANIFEST.MF 指纹对比,保证 MANIFEST.MF 文件没有被篡改
d. MANIFEST.MF 中的内容和 APK 所有文件指纹逐一对比,保证 APK 没有被篡改
1 | // 要校验的包放在 toVerify 的列表中 |
使用loadCertificates函数进行校验:
1 | for (ZipEntry entry : toVerify) { |
使用getInputStream函数初始化VerifierEntry结构,并返回验证item的zip的压缩数据流
1 | public InputStream getInputStream(ZipEntry ze) { |
调用readFullyIgnoringContents函数,读取待验证entry文件的数据流,对每个待验证entry进行校验,即和计算该文件的数字指纹和清单中记录的hash值进行比对
1 | // 读取完毕后,进行校验 |
如果校验失败,此时verifiedEntries为空,在loadCertificates后进行检查,为空时抛出异常
1 | final Certificate[][] entryCerts = loadCertificates(jarFile, entry); |
2.3.10.5. 判断是应用升级的情况
即installFlags携带INSTALL_REPLACE_EXISTING(adb 安装时指定了-r参数)
- 在mSettings.mRenamedPackages中查找该包名,如果能够查找到返回旧的包名,再检查Package的mOriginalPackages,如果 pkg的 mOriginalPackages 包含该旧包名,且该旧包能在PKMS.mPackages中查到,则说明该包已经安装,此时为应用升级的情况.在这种情况下,需更新当前的包名为旧包的包名,以及更新其所属组件的关联的packageName名称,并将replace标记标为true.
- 直接在PKMS.mPackages查找到了该包,说明包已经安装,只需要将replace设置为true
当replace为true时,检查版本号,在AndroidN版本上,如果新安装的包sdk小于22,而已安装的 >=22,跳出安装流程,不能从支持runtimepermission的跳到不支持的版本上
2.3.10.6. 当Packages.xml中存在package的记录时,比对签名
Quick sanity check that we’re signed correctly if updating; we’ll check this again later when scanning, but we want to bail early here before tripping over redefined permissions.
upgradeKeySets 这一项来自于解析AndroidManifest.xml节点的key-sets项,一般情况下,不会配置该项.
当旧包记录中keySetData不为空时,比对当前包中mSigningKeys是否包含这一项,包含则通过检查,没有包含,则设置错误提示退出安装.
1 | // 使用 KeySetManagerService 查看是否使用了有效的 upgradeKeySets |
demo: 通过checkUpgradeKeySetLP函数比对当前包的mSigningKeys 中是否包含 记录中的 KeySet对应的 public-key项,校验失败设置错误提示,退出安装
1 | <key-sets> |
旧包中keySetData项为空, 可以查看Packages.xml文件,对应的标签为upgrade-keyset, 没有该项,则通过
verifySignaturesLP
函数进行签名比对
2.3.10.6.1. verifySignaturesLP 进行签名比对(新老安装包)
在mSettings.mPackages中存在该包, 需要比对该包的签名和现在解析的包的签名进行比对.签名不一致,退出安装.
关于databaseVersion项在启动篇介绍过, 对于private volume. databaseVersion的值总是3,而public volume的值往往等于sdkVersion.
1 | // 首先记录中包的签名不为空 |
接下来检测shareUser的签名,即如果应用指定了shareUserId项,可将指定shareUser的项看作同类的包,需要挨个检测签名是否一致,以出现的第一个包的签名为基准.
1 | // Check for shared user signatures |
此处可以追溯pkgSetting.sharedUser.signatures.mSignatures第一次被赋值的地方.
在scanPackageDirtyLI函数处调用了mSettings.insertPackageSettingLPw(pkgSetting, pkg) 函数,此处检测shareUser的签名如果为空,会更新为当前扫描包的签名
1 | // If this app defines a shared user id initialize |
2.3.10.7. 验证权限
如果从apk中解析到的权限信息,在之前已经定义过,即mSettings.mPermissions包含该权限名,那么首先验证签名信息是否符合(升级包时,一般包),签名符合继续安装流程;签名不符合时,如果冲突的权限来自framework-res.apk,删除当前apk冲突的权限,继续安装流程,否则终止安装。
如果不是framework-res.apk包,需要其定义的权限是否为PROTECTION_DANGEROUS权限, 如果是dangerous权限,但已经存在的同名权限的类型为非dangerous的权限,则需要变更当前权限的protectionLevel为记录中的protectionLevel
原因是防止特权扩大,如某应用在其他应用组中添加了该权限,如果重新定义该权限名称为dangerous权限,则会导致之前的应用组的该权限被默认授权.??
2.3.10.8. 更新指令集以及拷贝本地库并执行dexopt优化
可以参考处理本地库目录, 以及为非系统应用更新共享库信息.
由于本地库目录的变更, 一些应用可能使用到了此库,所以需要更新这些应用的usesLibraries项,更新路径信息.
1 | else if (!forwardLocked && !pkg.applicationInfo.isExternalAsec()) { |
2.3.10.9. 目录更名
调用FileInstallArgs.doRename方法,将/data/app/vmdl<随机整数>.tmp目录更名为/data/app/packageName-num,num的值在1和2之间循环取值,同时还要更新FileInstallArgs和Package pkg相关的路径信息:
1 | boolean doRename(int status, PackageParser.Package pkg, String oldCodePath) { |
冻结应用,杀死进程.但如果安装参数中携带了INSTALL_DONT_KILL_APP标记时,不会杀掉该进程.
更名完毕后,根据是全新安装还是覆盖安装,执行全新安装installNewPackageLI或覆盖安装replacePackageLIF方法。
2.3.10.10. 覆盖安装
replacePackageLIF函数进行覆盖安装
首先,对于重复安装时先比对 新包和 已安装包的签名信息是否匹配,对于升级安装时验证新包是否包含旧包的所有签名密钥,失败直接返回,停止覆盖安装
1 | // verify signatures are valid |
如果旧包的restrictUpdateHash字段不为空,且旧包为system app时,需要对apk进行MD校验, 只有当apk的md值等于该restrictUpdateHash字段的值时,才允许升级.
该字段来自于解析apk的Manifest节点, restrict-update : android:hash.
don’t allow a system upgrade unless the upgrade hash matches
接下来检查新老包的shareUserid有没有发生变化,包括其parent和child包的也要一并检查.如果shareUserId变化了,提示错误,退出安装.
1 | // Check for shared user id changes |
查询为哪些用户安装了旧包,并保存起来
1 | installedUsers = ps.queryInstalledUsers(allUsers, true); |
更新PackageInstalledInfo.removedInfo信息
对应于PackageRemovedInfo removedInfo类
最后根据apk的类型进行系统级/非系统级替换安装:
2.3.10.10.1. 非系统级apk覆盖安装
调用replaceNonSystemPackageLIF方法进行非系统级apk的覆盖安装,与全新安装的区别主要是,在安装前会先删除内部数据结构信息,从而可以进行扫描安装:
首先,调用deletePackageLI方法删除已安装apk的数据结构信息,但是保留数据目录。对于ForwardLocked/External(是否忘记porting InternalSd)类型的apk,在杀死该应用前发送一个广播,使用户可以放弃资源:
1 | if (deletedPackage.isForwardLocked() || isExternal(deletedPackage)) { |
调用clearAppDataLIF方法清除所有用户下的该应用的DE区和CE区的/code_cache目录.(通过installd完成)
1 | clearAppDataLIF(pkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE |
调用clearAppProfilesLIF方法清除 /data/misc/profiles/ref/
清除所有用户下的该文件 /data/misc/profiles/cur/
接下来执行的步骤和全新安装流程基本相同, 即执行scanPackageTracedLI进入scanPackageLI方法扫描安装apk,调用updateSettingsLI方法(主要调用了updateSettingsInternalLI方法),将新安装的apk信息更新到packages.xml中.跳转到此
但是需要更新新安装包PackageSetting的oldCodePaths字段(携带SCAN_DONT_KILL_APP
标志时,即不杀应用时),该字段保存了旧包的安装包baseapk即splitapk的路径, 新安装包的子包的此字段也需要同步更新.
至此,非系统级apk覆盖安装结束,最终回到installPackageLI中。
2.3.10.10.2. 系统级apk覆盖安装
调用replaceSystemPackageLIF函数进行系统级apk的覆盖安装
首先需要调用removePackageLI方法清除旧包的数据结构信息: 从PackageManagerService.mPackages中清除已安装的系统包,接着调用
cleanPackageDataStructuresLILPw
从PackageManagerService.mProviders/mServices/mActivities等组件中清除旧包所包含的组件信息;
1 | removePackageLI(deletedPackage, true); |
接着,调用mSettings.
disableSystemPackageLPw
方法将已安装的包信息disabled:先根据包名查询mSettings.mDisabledSysPackages是否存在包信息,如果存在,直接返回false;不存在,更改标志为FLAG_UPDATED_SYSTEM_APP,将已安装的包信息添加到mSettings.mDisabledSysPackages中,并根据已安装的包信息创建一个新的备份,用这个备份来更替换mSettings.mPackages的包信息,以及替换共享用户的包信息/mSettings.mUserIds或mSettings.mOtherUserIds(即,要保留原始安装包信息,只对备份修改):
省略了子包的相关操作….
1 | // 将已安装的包信息disabled |
旧包没在mDisabledSysPackages中时,说明原先的包是安装状态,需要填充PackageInstalledInfo.PackageRemovedInfo信息. 调用createInstallArgsForExisting方法生成这个信息, We need to make sure to delete the older one’s .apk.
执行clearAppDataLIF 和 clearAppProfilesLIF 清除codecache和primary.prof 文件;
设置ApplicationInfo.FLAG_UPDATED_SYSTEM_APP 标志,通过isUpdatedSystemApp查询时返回true
执行scanPackageTracedLI(#scanPackageTracedLI)函数进行安装.
设置应用的安装时间和更新时间firstInstallTime lastUpdateTime, 还有子包的
遍历当前包的所有子包,检查更新包的子包是否和旧包的子包相同. 旧包子包没有在更新包中,需要将该包删除(从mPackages中清除,并包括组件信息等),并清除数据(不止是codecache,没有
DELETE_KEEP_DATA
标志的前提下)调用updateSettingsLI方法(主要调用了updateSettingsInternalLI方法),将新安装的apk信息更新到packages.xml中
调用prepareAppDataAfterInstallLIF方法为新安装的包准备数据目录
如果安装失败,调用removeInstalledPackageLI删除新的包,扫描恢复旧的安装包,若是第一次更新从mSettings.mDisabledSysPackages删除旧的包信息,更新安装的包名,并再次更新packages.xml文件
1 | if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) { |
至此,系统级apk覆盖安装完成,最终回到installPackageLI。
2.3.10.11. 全新安装
调用PackageManagerService. installNewPackageLI方法执行全新安装。
如果当前包已安装过,即使是更名的,也结束installNewPackageLI安装:
1 | if (mSettings.mRenamedPackages.containsKey(pkgName)) { |
调用PackageManagerService.scanPackageLI方法扫描安装apk,创建数据目录,主要创建/data/data/
然后调用updateSettingsLI方法(主要调用了updateSettingsInternalLI方法),将新安装的apk信息更新到packages.xml中:
- 首先,先更新packages.xml文件,此时该apk为安装未完成状态;
- 调用updatePermissionsLPW,为安装的apk分配权限,并将相应的gid号保存到PackageSettngs或SharedUserSetting的gids数组中;
- 更新安装状态为安装完成,和PackageInstalledInfo res的属性,重新更新packages.xml文件。
1 | private void updateSettingsInternalLI(PackageParser.Package newPackage, |
2.3.10.11.1. 准备数据目录
调用prepareAppDataAfterInstallLIF方法为新安装的包准备数据目录
1 | private void prepareAppDataAfterInstallLIF(PackageParser.Package pkg) { |
installNewPackageLI方法完成,回到installPackageLI方法;
否则,调用deletePackageLI方法,删除安装的数据信息和数据/缓存目录,除非这些目录在安装前已经存在,然后返回installPackageL方法:
2.3.11. 小结
在安装完成后,更新已安装的用户id,保存到res的newUsers中.
1 | final PackageSetting ps = mSettings.mPackages.get(pkgName); |
至此,InstallPackageLI方法结束,回到processPendingInstall方法继续处理。
2.3.12. processPendingInstall方法后续处理
installPackageLI安装完成后,首先调用args.doPostInstall方法,确保当安装失败时删除复制的apk、资源和库。
1 | // A restore should be performed at this point if (a) the install |
根据之前的记录的RemoveInfo的信息, 覆盖安装的情况下,此信息才不为空.
当removeinfo为空(全新安装的情况),且安装包对应的applicationinfo中有FLAG_ALLOW_BACKUP标记时(来自于Manifest), doRestore为true
doRestore为true时,连接BACKUP_SERVICE,执行BackupManagerService.restoreAtInstall方法, 最终执行到 PerformUnifiedRestoreTask 的execute方法, startRestore方法 ??
接着,将安装次序和安装信息保存到PackageManangerService.mRunningInstalls中:(mNextInstallToken>=1)
1 | token = mNextInstallToken++; |
doRestore为false的情况下, 即覆盖安装的情况下, 或者前面的restoreAtInstall方法执行错误的情况(可能是没有 BackupManagerService , 或连接不上时)
执行POST_INSTALL阶段
2.3.13. POST_INSTALL阶段(处理POST_INSTALL消息)
这部分主要发送一些安装成功的广播,可以被launcher接收,在桌面添加图表等操作,并回调Observer安装成功的接口,具体处理流程如下:
从mRunningInstalls中取出之前保存的 PostInstallData对象
1 | PostInstallData data = mRunningInstalls.get(msg.arg1); |
2.3.13.1. handlePackagePostInstall函数的处理
主要看handlePackagePostInstall的实现, 该函数中第一个参数为 PackageInstalledInfo , 该参数为之前保存安装结果的result, 里面保存了错误类型,安装进度,removedinfo信息等.
如果其中removedInfo不为空,发送android.intent.action.PACKAGE_REMOVED的广播,提示旧包被删除,广播中携带参数killapp(是否杀掉相应进程)
a. 如果对应的removedinfo中的dataRemoved为true, 且不是删除的system app, 则另外发送android.intent.action.PACKAGE_FULLY_REMOVED的广播
如果对应的removedAppId>=0,发送android.intent.action.UID_REMOVED的广播, 在前面的步骤中,removedAppId赋值是在覆盖系统包时,如果系统包旧包含有子包的情况下,且旧包的某子包未在新安装的包的子包中时,会涉及到该种情况
如果携带的参数中包含grantPermissions, 赋予运行时权限,并更新packages.list文件
如果其parent包不为空,查看parent包是否在mDisabledSysPackages 禁用包中, 且该包为特权包, 给其赋予申请的运行时权限
无论是否携带 grantPermissions对应的安装时参数时
1 | if (res.pkg.parentPackage != null) { |
2.3.13.1.1. 设置用户集firstUsers和updateUsers
设置第一次添加该包的用户集firstUsers和更新该包的用户集updateUsers:当res.origiUsers为空时,只设置发firstUsers为res.newUsers;当存在res.origiUsers时,将res.newUsers中不存在于res.origiUsers的用户存入firstUsers,其余的存入updateUsers中。
1 | // Determine the set of users who are adding this package for |
2.3.13.1.2. 向用户集发送包广播
然后,向firstUsers和updateUsers中的用户发送ACTION_PACKAGE_ADDED “android.intent.action.PACKAGE_ADDED” 广播:
- 对于覆盖安装的情况, 并向updateUsers的用户发送包替换 ACTION_PACKAGE_REPLACED (“android.intent.action.PACKAGE_REPLACED” ) 广播
- 对于全新安装的情况, didRestore 为true,但此时传入的参数为null, 且非系统包的情况下,发送ACTION_PACKAGE_FIRST_LAUNCH的广播, 在这个安装场景下,这条是不符合条件的
- 并对FORWARD_LOCKED/外置SD卡的包发送ACTION_EXTERNAL_APPLICATIONS_AVAILABLE的广播
2.3.13.1.3. 为firstUsers 新安装用户集处理挂起的运行时权限申请请求
在mRestoredUserGrants中取出挂起的权限申请请求,然后从其中找出当前包申请的权限,对其进行权限授予,更新flag,并更新/data/users/
1 | for (int userId : firstUsers) { |
2.3.13.1.4. 覆盖安装情况下删除旧包的相关文件
调用res.romovedInfo.args.doPostDeleteLI方法删除旧包的代码/资源/本地库/dex文件等。
1 | boolean doPostDeleteLI(boolean delete) { |
2.3.13.1.5. 回调监听器的onPackageInstalled接口
最后,当在PackageInstalled.apk安装时指定了包安装监听器,回调该监听器的onPackageInstalled接口,告知监听的应用包已经安装完成. Bundle在成功安装时为null:
1 | Bundle extras = extrasForInstallResult(res); |
处理完后,安装结束
1 | Log.d(TAG, "pms install end"); |
2.4. 剩余消息处理
- 处理MCS_RECONNECT消息
当在复制apk时抛出RemoteException,startCopy会发出MCS_RECONNECTION消息,尝试重新复制和安装:如果,当前绑定到MCS上,解除绑定;然后重新绑定该服务,如果绑定失败,处理所有安装请求的错误信息,并移除所有的安装请求
- 处理MCS_GIVE_UP消息
当尝试4次复制apk,均失败时,在startCopy中发送该消息将此安装请求从队列中移除:
- 处理MCS_UNBIND消息
当前没有安装请求时,会解除与MCS服务的绑定;如果此时又来了安装请求,那么会再次发送一个MCS_BOUND的空消息,继续进行安装