Android应用安装过程深度解析

By xia0

Android应用安装过程深度解析

本文由我和willing对整个安装过程的原理梳理,感谢willing精心的绘图以及相关源码整理。

开始

关于对Android应用的安装过程梳理,是因为很多重打包的外挂利用系统矫正器来绕过安卓系统的签名校验。为了分析其原理,详细分析了Android应用安装的过程,找到其中签名校验的地方,进一步做一些对抗和检测。

整体流程

一个 Android 应用安装到手机上大致分为四种情形:

  • 系统应用,在设备每次启动时完成安装
  • 通过 adb install 命令安装
  • 应用市场安装,封装安装过程(略)
  • 第三方应用安装或双击安装包,会启动系统应用引导安装

无论采用哪种安装方式,最终的安装过程都会走到 PackageManagerService,由这个类来完成一系列的工作。PackageManagerService 实际上是一个系统服务,负责提供系统上所有应用的管理,包括安装、优化、查询和卸载,这个类最终会通过 socket 与 installd 这个守护进程通信,真实对应用进行操作的其实是由 installd 这个进程完成。

场景一:系统应用安装

在设备启动时,Linux 系统的用户空间进程 init (pid = 1)会孵化许多子进程,包括一系列守护进程,其中就有上面提到的 installd,然后会孵化许多重要的系统服务,如 servicemanager,这个进程会负责管理所有服务的 Binder 通信,最后 init 还会孵化 zygote 进程,这个进程是第一个 Java 进程,zygote 进程会孵化出许多应用程序进程,包括 launcher 进程,即桌面进程,zygote 还会孵化出 system_server 进程,负责启动和管理 Framework 的 Java 层服务,包括 PackageManagerService、ActivityManagerService 等。

image-20190801151901799

补充:system_server 的启动与运行流程

system_server 由 zygote 进程孵化,是整个 Android Framework 的基础

zygote 启动阶段会调用 forkSystemServer() 创建子进程 system_server

image-20190801152013042

ForkAndSpecializeCommon 函数中完成进程创建工作并返回,两次返回,pid=0 时为子进程,pid>0 时为父进程。onZygoteInit() 回调的实现在在 app_main.cpp 中,会调用 ProcessState::self() 初始化 binder 的交互操作,然后调用 proc->startThreadPool() 创建 binder 线程进行通信

RuntimeInit 的 findStaticMain() 调用 com.android.server.SystemServer 的 main 方法

SystemServer 的 run() 方法中会完成以下工作:

  • 设置时间、语言等,设置虚拟机库文件,设置内存
  • Looper.prepareMainLooper() 主线程 looper 运行在当前线程
  • 加载库文件 android_servers,位于 /frameworks/base/services
  • createSystemContext() 初始化系统上下文
  • SystemServiceManager() 创建系统服务管理器,并添加到本地服务成员 LocalServices 中
  • 启动各种系统服务,包括引导服务、核心服务、其他服务
  • 开启循环,等待其他线程通过 handler 发送消息到主线程来处理

在 system_server 进程启动过程中会启动各种系统服务,其中就包含 PackageManagerService

image-20190801152050671

PackageManagerService 类构造函数中完成了许多工作,主要包含:

  • 创建 Settings 对象,添加系统的 SharedUser 信息
  • 为 Installer 对象赋值,赋值参数是在 system_server 进程中创建的
  • 创建 PackageDexOptimizer 和 DexManager 对象
  • 获取 SystemConfig 实例,获取系统的全局配置信息,如 GlobalGids、SystemPermissions、AvailableFeatures
  • 创建 ServiceThread 和 PackagerHandler 对象
  • 创建 UserManagerService,支持多用户
  • 读取系统定义的权限保存到 permConfig 变量中
  • 读取系统共享库保存到 libConfig 变量中
  • 解析 package.xml 文件中的内容保存到 ArrayMap 结构中
  • 配置扫描参数 scanFlags
  • 开始扫描系统应用,包括目录 /vender/overlay、/system/framework、/system/priv-app、/system/app、/vendor/app、/oem/app
  • 开始扫描非系统应用,包括目录 /data/app、/data/app-private
  • 启动内存垃圾回收
  • 启动私有服务 PackageManagerInternal

总结来说主要是这几项内容:

  • 创建了 Java 层 Installer 和 C 层 installd 之间的 socket 连接
  • 扫描各目录下 apk文件完成安装(建立各安装包的配置结构信息,并添加到全局列表中进行管理)
  • 创建了 PackageHandler 对象并建立消息循环,接收外部的安装请求
  • 解析权限,建立底层 Linux Kernel 的用户机制和虚拟机层权限机制之间的映射

详细说明各项:

Installer 是 PackageManagerService 的一个内部类,继承自SystemService,协助完成安装过程,主要是进行 apk 格式转换和数据目录建立,封装对文件和路径操作,真正的安装工作交由 IInstalld 内部类对象完成。

image-20190801152128607

Installer 在 SystemServer 中启动,通过 SystemServiceManager 的 startService(Installer.class) 启动。

image-20190801152145965

构造函数 Installer() 初始化 context 和 isolated(是否真正连接installd)成员变量

重写 onStart() 方法,基于 isolated 判断是否调用 connect() 进行连接

connect() 通过 ServiceManager 获取 installd 服务并拿到服务的本地代理放到成员变量 mInstalld 中

Installer 调用 dexopt() 最终调用了 mInstalld 的dexopt() 方法,其他方法类似,几乎就是一个 IInstalld 类的封装

!!!! Installer 的实现变化很大:6.0 上通过以命令的方式通过 execute 方法执行;8.1 通过 AIDL 接口实现

PackageManageService(system权限) 与 installd(root权限) 守护进程共同完成应用管理

PackageManagerService 的构造函数中会扫描许多应用目录来安装 apk

包括:

  • 系统应用,目录有 /vender/overlay/system/framework/system/priv-app/system/app/vendor/app/oem/app
  • 非系统应用,目录有 /data/app/data/app-private

调用的方法是 scanDirTracedLI()

image-20190801152328199

可以看到依次调用 scanDirLI()、scanPackageLI()、scanPackageInternalLI()

其中,scanDirLI() 调用了 ParallelPackageParser 类进行解析,从名字也能看出做了一些并行的优化处理,真正的工作还是交给 PackageParser 来完成,PackageParser 的 parsePackage() 方法内部完成了应用的解析,实际上是解析了 AndroidManifest.xml 文件里面各个标签,包括包名、版本、权限、各组件这些信息,最后的解析结果放在 PackageParser.Package 类中返回给 PMS,这样 PMS 就拥有应用的信息便与做各种管理

后面继续调用了 scanPackageLI() 和 scanPackageInternalLI() 是进一步检测应用是否需要更新,如果需要更新则接着调用 createInstallArgsForExisting() 构造安装参数开始安装(后面还会遇到这个函数,暂时不展开),安装涉及到的签名校验也是在这里,调用了 collectCertificatesLI()、compareSignatures() 等

场景二:adb 命令安装

adb 是 Android SDK 提供的一个命令行工具,为客户端-服务器架构,服务端为移动端上运行的守护进程 adbd,与 installd 一样由 init 进程创建

源码路径为:http://androidxref.com/8.1.0_r33/xref/system/core/adb/

image-20190801152448109

adb_commandline() 函数接收 adb 命令行参数,调用 install_app() 执行安装流程,这个函数会对命令行做一些修改,实际上调用 pm 命令,并通过 send_shell_command() 将命令发送给 Pm 类。

Pm 启动函数 main 会构造 Pm 类对象并调用 run() 方法,在这个方法里首先会获取 PackageManger 服务 mPm,然后调用 getPackageInstaller() 获取 PackageInstaller 对象 mInstaller,最后基于不同的命令调用不同的方法。

runInstall() 里面会首先获取之前解析时设置的安装参数 InstallParams,然后依次调用 doCreateSession() 创建安装会话,doWriteSession() 会通过 IO 流将应用信息写入 PackageInstaller.Session 对象,doCommitSession() 会提交相应的会话启动安装,最后调用 abandonSession() 结束会话。

image-20190801152518924

PackageInstallerSession 的 commit() 方法首先将包信息封装到 PackageInstallObserverAdapter 这个类中,是 PackageInstallerService 的一个内部类,然后通过这个适配器获取 PackageInstallObserver2 对象代理,这是一个跨进程的观察者,接收安装状态的回调,最后用 Handler 的 obtainMessage 构造一个 MSG_COMMIT 类型的消息并发送出去。

发送的消息由 PackageInstallerService 的 Handler.Callback 接受,然后调用 commitLocked(),此方法最终会调用 PMS 的 installStage() 方法。

PackageInstallObserver2 对象接收到安装成功的回调时,会接着调用 PackageInstallerService 的 dispatchSessionFinished(),这个方法最后也是到了 PMS 里面调用 sendSessionCommitBroadcast() 方法。

场景三:第三方应用安装

当下载一个新的应用包双击安装时,实际上是发送了一个 “android.intent.action.INSTALL_PACKAGE” 的 intent,这个时候会唤起 Android 系统的一个应用 PackageInstaller 来完成安装操作

PackageInstaller 的应用代码位于:http://androidxref.com/8.1.0_r33/xref/packages/apps/PackageInstaller/

安装位置 /system/priv-app/GooglePackageInstaller,是一个 Android 系统默认应用程序,提供了用户界面来管理应用或包,用于安装普通文件

image-20190801152605993

应用双击安装后会唤起 PackageInstaller 应用,入口类为 InstallStart,onCreate() 首先被调用完成一些初始化过程,然后获取调用源做一些权限检查。

然后会调起 PackageInstallerActivity,onCreate() 会包括获取 PackageManager 和 PackageInstaller 对象,然后从 intent 中获取 mSessionId、packageUri、mOriginatingURI、mReferrerURI 这些与安装包相关的信息,接着调用 processPackageUri() 解析 uri,这里面会调用 PackageParser 去解析包,再接着会调用 bindUi() 完成安装主界面的展示和部件绑定,最后调用 checkIfAllowedAndInitiateInstall() 检查是否允许安装,如果允许,里面会继续调用 initiateInstall() 初始化安装,实际上这个函数是检查是否需要替换安装,最后调用 startInstallConfirm() 准备安装,设置 mOk 这个安装按钮为 true。

用户确认点击安装按钮后,调用 startInstall() 开始安装,这个函数里面又会构造一个 intent 传递必要的数据,开启 InstallInstalling 这个 activity。

image-20190801152635344

InstallInstalling 这个类会拿到应用数据开启真正的安装流程。

首先是 onCreate() 函数,首先从 Intent 中取出数据放到 ApplicationInfo 中,然后基于拿到的应用信息分两种情况:如果是更新,则调用 getPackageManager() 获取 PackageManager 然后调用 installExistingPackage() 安装,成功调用 launchSuccess() 方法,失败调用 launchFailure() 方法,launchSuccess 会构造 Intent 跳转到 InstallSuccess 类, launchFailure 则跳转到 InstallFailed 类;如果是新应用的安装,首先会向 InstallEventReceiver 注册一个安装结果监听的广播,成功调用 launchSuccess,失败调用 launchFailure。

接下来是 onStart() 方法,这里会依次调用 getPackageManager(),getPackageInstaller(),registerSessionCallback() 注册会话的回调。

最后是 onResume() 方法,这个方法里会获取 session 的相关信息,然后创建并执行 InstallingAsyncTask() 的安装服务,其中的 doInBackground() 方法会将应用信息通过 IO 流写入 PackageInstaller.Session 中,onPostExecute() 方法则构造一个 PendingIntent 并将它的 IntentSender 通过 PackageInstaller.Session 的 commit() 方法发送出去

这里就跟 adb 安装走到了同一个流程中。

深入 PMS 中的安装

上面的后面两种场景最后都会通过 PackageInstallerSession 的 commit() 方法提交一个安装的会话,并且最终调用到了 PackageManagerService 的 installStage() 方法

image-20190801152711796

在 installStage() 里会基于传入的参数构造一个 InstallParams 对象,这个对象中包含安装包的所有数据,然后将这个对象作为消息内容,通过 mHandler 发送一个类型为 INIT_COPY 的消息。

所有类型的消息处理都在 PackageHandler 里,这个类会通过 Binder 与 system_server 创建的 ServiceThread 线程绑定,PackageHandler 负责分发任务,ServiceThread 负责处理任务。

1、处理 INIT_COPY 消息

首先从消息结构中取出前面封装好的 HandlerParams 对象,然后检查 mBound 标志判断是否绑定 DefaultContainerService,这个服务用于检查和复制文件,位于 com.android.defcontainer 进程,通过 IMediaContainerService 与PMS 通信,mBound 默认为 false,此时调用 connectToService() 来绑定服务,并获取当前索引值的 HandlerParams 对象添加到 mPendingInstalls 这个 HandlerParams 的 ArrayList 中,如果 mBound 为 true,即服务已经连接,则直接添加 HandlerParams 对象,并发送一个 MCS_BOUND 消息。

2、处理 MCS_BOUND 消息

首先检查 DefaultContainerService 服务连接没问题,就开始处理安装请求队列,从 mPendingInstalls 中取出 HandlerParams 对象并调用它的 startCopy() 方法。

image-20190801152741833

HandlerParams 是 PMS 中的抽象类,它的 startCopy() 会通过一个 mRetries 的私有变量检查尝试次数,超过4次则向 Handler 发送 MCS_GIVE_UP 的消息,抓到异常则发送 MCS_RECONNECT 的消息,成功则继续调用 handleStartCopy() 这个抽象方法,具体实现在 InstallParams 中,

InstallParams 的 handleStartCopy() 会首先是确定应用安装位置,是sd卡还是内部存储,并且做一些检查如位置冲突,空间够不够,然后通过 createInstallArgs() 创建 InstallArgs 类,然后调用这个类的 copyApk() 方法,InstallArgs 类也是抽象类,定义了应用包安装逻辑,子类包括 FileInstallArgs,处理 non-ASEC 应用,也即内部存储空间的应用, AsecInstallArgs 处理安装到 SD 卡中的应用,以及 MoveInstallArgs 处理已安装的应用,因此实际上是调用各自实例对象的 copyApk() 方法

以 FileInstallArgs 为例,copyApk() 方法会调用 doCopyApk(),在这个方法里会首先创建一个临时存储目录,然后调用 DefaultContainerService 的 copyPackage() 方法完成 apk 复制

HandlerParams 复制 apk 后会调用 handleReturnCode(),这个方法又会调用 processPendingInstall() 方法,最终会调用到 InstallArgs 的 doPreInstall() 完成安装前的清理工作,调用 installPackageTracedLI() 实现真正的安装,以及调用 InstallArgs 的 doPostInstall() 完成收尾的清理工作,最后发送一个类型为 POST_INSTALL 的消息

image-20190801152755489

installPackageTracedLI() 里面完成的是真正的安装操作,会依次进行变量初始化,再次解析应用包,获取签名信息,权限检查,安装路径重命名等等操作,最后根据参数有两种不同的处理方式,更新条件下的覆盖安装,调用 replacePackageLI() 方法,以及应用的首次安装,调用 installNewPackageLI() 方法

其中 replacePackageLI() 会首先清楚应用包的注册信息,然后类似 installNewPackageLI() 里面的流程重新安装更新的包,直接看 installNewPackageLI() 流程

installNewPackageLI() 里面调用 scanPackageTraceLI() 执行安装,然后这个函数里面又会继续调用 scanPackageInternalLI() 执行包解析,最后将 PackageParser.Package 对象返回出来,扫描完包之后就会调用 updateSettingsLI() 更新 Settings,也就是更新应用在 PMS 中的注册信息,会修改包括 packages.xml、package.list 等相关文件,如果安装成功则调用 prepareAppDataAfterInstallLIF() 来创建相关的数据目录,失败则执行回退操作 deletePackageLIF()

3、处理 POST_INSTALL 消息

首先从 mRunningInstalls 中拿到 PostInstallData 对象,mRunningInstalls 是 PostInstallData 格式的列表,在应用安装成功时构建,在安装完成后会调用 delete() 移除这一条记录,接下来取 PostInstallData 对象中的一些信息如安装参数,授予的权限等作为参数调用 handlePackagePostInstall() 执行安装完成的后续操作,包括授予权限,发送安装成功广播,发送启动广播等等

安装过程小结

与应用相关的一些目录

  • /system/priv-app:系统应用安装路径,Android 4.4+ 开始出现,区分系统应用权限,拥有 SignatureOrSystem 权限,此目录下的 service 具有保活能力

  • /system/app:系统应用安装路径,权限略低于 priv-app 目录下的应用,放置比如厂商内置应用

  • /data/app:用户应用安装路径,应用安装时将 apk 复制到此目录下

  • /data/data:用户应用数据存放路径,存在沙箱隔离

  • /data/dalvik-cache:存放应用的dex 文件

  • /data/system:存放应用安装相关文件

    • packages.xml 是一个应用的注册表,在解析应用时创建,有变化时更新,记录系统权限,各应用信息,如name, codePath, flag, version, userid,下次开机时直接读取并添加到内存列表
    • package.list 指定应用的默认存储位置,userid 等

应用安装过程总结

  1. 将应用 apk 拷贝到指定目录下
  2. 解压 apk,将 dex 文件拷贝到 /data/dalvik-cache 目录,创建 /data/data/ 数据目录
  3. 解析 AndroidManifest.xml 及其他资源文件,提取应用包信息,注册到 packags.xml 中
  4. 由 Launcher 进程通过 PMS 取出所有应用程序,展示在桌面上

PMS 与 PM 与APM

前面的一切分析都是围绕 PackageManagerService 来进行的,但是实际上平时开发过程中基本接触不到这个类,在用户层用得最多的应该是 PackageManager 这个类

image-20190801152858658

通常在用户层通过 Context 的 getPackageManager() 方法来获取 PackageManager 实例,Context 的 getPackageManager() 实现在 ContextImpl 里面,这个函数实际上返回的是一个 ApplicationPackageManager 对象,ApplicationPackageManager 类构造函数需要两个璨是,一个是 ContextImpl 自身,另一个是通过 ActivityThread 的 getPackageManager() 获取 IPackageManager 对象

ActivityThread 的 getPackageManager() 首先调用 ServiceManager 的 getService(“package”) 获取 IBinder 对象,然后通过 IPackageManger.Stub.asInterface(IBinder) 获取 IPackageManager 对象,实际上为这个对象的一个代理

继续梳理一下这个类与 PMS 之间的关系

image-20190801152915992

IPackageManager 使用了 Android 接口定义语言(AIDL)实现进程间通信,通过在 .aidl 后缀的文件中定义好接口,Android SDK 就会基于此文件自动生成一个 IBinder 接口的类,服务端实现这些接口,客户端则通过绑定到服务调用接口中定义的方法

IPackageManager 通过 AIDL 文件自动生成的一个接口类,继承自 IInterface

IPackageManager.Stub 是 IPackageManager 的内部类,继承自 Binder,实现 IPackageManager

  • asInterface() 方法接受 IBinder 对象,转换成 IPackageManager 类型对象并返回,实际上返回的是 IPackageManager.Stub.Proxy 对象
  • asBinder() 方法返回自身,即 IPackageManager.Stub 对象
  • 重写了 onTransact() 方法,根据命令类型处理数据传输

IPackageManager.Stub.Proxy 是 IPackageManager.Stub 的内部类,实现了IPackageManager接口

  • mRemote对象是IBinder类型,是对IPackageManager.Stub的引用
  • 重写了asBinder()方法,返回mRemote
  • 实现了AIDL文件定义的接口

PackageManager 是系统提供的服务管理类,负责管理应用程序包的,包含以下功能:

  • 安装/卸载/更新应用,包括 installPackage/
  • 查询已安装应用,通过 getInstalledPackages() 方法获取 PackageInfo 对象的列表
  • 查询应用相关信息,通过 getPackageInfo() 方法获取 PackageInfo 对象
  • 查询权限相关信息,通过 getPermissionInfo() 方法获取 PermissionInfo 对象
  • 增加/删除权限,包括 addPermission/removePermission
  • 清除用户数据/缓存/代码等

PackageManager 是一个抽象类,定义了 IPackageManager 接口的子集

ApplicationPackageManager 是 PackageManager 的实现类,它继承了 PackageManager 并实现其所有抽象方法

PackageManagerService 继承了 IPackageManager.Stub

因此,总结来说就是,ApplicationPackageManager 是 AIDL 模型中 Binder 通信的客户端,PackageMangerService 就是服务端,客户端的所有的功能都是通过一个 IPackageManager 类型的成员变量去完成的,实际上这个变量就是 PackageManagerService 的一个代理,通过这个代理客户端可以调用到 PackageManagerService 中的一些方法

参考