4. APK安装流程源码分析
理解 APK 安装流程,是 PackageManager 系统定制的起点。无论是预置应用、静默安装、增量安装,还是定制安装校验策略,最终都要落到 PackageInstaller 与 PackageManagerService(PMS)这条主链路上。本节从源码调用顺序出发,把安装拆成五个阶段:安装请求、会话创建、文件拷贝、解析校验、提交落库,并说明每个阶段可定制的切入点。
一、核心原理:安装是一次跨进程的会话式提交
应用层调用 PackageInstaller 发起安装时,真正执行安装的是 system_server 中的 PMS。二者通过 Binder 通信,并以 PackageInstallerSession 作为中间载体。安装不是一次函数调用就完成,而是“先建会话、再写数据、最后 commit”的三段式过程,这样设计是为了支持分片传输、断点续传、多 APK 拆分包以及安装前的用户确认。
关键角色分工如下:
PackageInstaller:应用可访问的入口,负责创建会话、写入 APK 数据、发起 commit。PackageInstallerService:运行在 system_server,管理所有会话的生命周期与持久化。PackageInstallerSession:单个安装会话的状态机,负责暂存文件、校验参数、触发 PMS 安装。PackageManagerService:真正的安装执行者,负责解析、校验、优化、落库与广播。Installd:native 守护进程,负责创建应用数据目录、dexopt、seinfo 上下文等底层操作。
二、方法流程:五个阶段的源码链路
下面按调用顺序梳理主链路,括号内为常见类与方法名,便于在 AOSP 中定位。
- 安装请求:应用调用
PackageInstaller.createSession(),传入SessionParams(安装位置、安装原因、是否豁免校验等)。该请求经 Binder 进入PackageInstallerService.createSessionInternal(),生成 sessionId 并持久化到/data/system/install_sessions.xml。 - 会话创建:
PackageInstallerSession被创建并进入SESSION_CREATED状态,同时准备好暂存目录/data/app/vmdl<sessionId>.tmp。 - 文件拷贝:应用通过
openWrite()拿到输出流,把 APK 写入暂存目录;若是拆分包,则按 split 名称分别写入。此阶段只做落盘,不做解析。 - 解析与校验:commit 后进入
PackageInstallerSession.commit(),最终调用PackageManagerService.installStage(),在PackageInstaller的 handler 线程中执行installPackagesTracedLI(),完成PackageParser解析、签名校验、SDK 版本与权限校验、ABI 兼容性检查。 - 提交落库:校验通过后,PMS 调用
Installd创建数据目录并执行 dexopt,随后把PackageSetting写入packages.xml,更新PackageManagerService.mPackages内存映射,最后发送ACTION_PACKAGE_ADDED广播。
三、案例场景:一次静默安装的完整调用
以系统应用调用静默安装为例,典型代码如下:
PackageInstaller installer = context.getPackageManager().getPackageInstaller();
SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
params.setInstallLocation(PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
params.setInstallReason(PackageManager.INSTALL_REASON_POLICY);
int sessionId = installer.createSession(params);
try (OutputStream out = installer.openSession(sessionId).openWrite("base.apk", 0, apkLength)) {
copy(apkFile, out);
}
installer.getSession(sessionId).commit(IntentSender);
对应日志中可观察到 PackageInstallerSession 状态迁移:SESSION_CREATED → SESSION_OPEN → SESSION_SEALED → SESSION_COMMITTED。若在 commit 后失败,日志通常出现在 PackageManagerService 的 installPackagesTracedLI 阶段,例如签名不匹配、INSTALL_FAILED_VERSION_DOWNGRADE 或 INSTALL_FAILED_INVALID_APK。
四、关键阶段与定制点对照
| 阶段 | 核心类/方法 | 关键产物 | 可定制方向 |
|---|---|---|---|
| 安装请求 | PackageInstaller.createSession | SessionParams、sessionId | 默认安装位置、安装原因、豁免校验 |
| 会话创建 | PackageInstallerService.createSessionInternal | install_sessions.xml | 会话持久化策略、并发会话数 |
| 文件拷贝 | PackageInstallerSession.openWrite | vmdl<id>.tmp 下的 APK | 暂存路径、分片写入校验 |
| 解析校验 | PackageParser / PackageManagerService.installStage | PackageParser.Package | 签名校验、SDK 与权限白名单 |
| 提交落库 | Installd、packages.xml 写入 | PackageSetting、数据目录 | dexopt 参数、广播与默认权限 |
五、误区与复盘
误区一:把 createSession 当成安装完成。createSession 只创建会话,APK 尚未进入 PMS 解析流程,此时取消不会产生任何安装副作用。
误区二:在 openWrite 阶段做签名校验。该阶段只有文件流,没有解析结果,签名校验必须放到 PMS 的解析阶段,否则会误判拆分包与 v2/v3 签名。
误区三:忽略 sessionId 与暂存目录的对应关系。定制清理逻辑时若直接删除 /data/app/vmdl*.tmp,可能破坏正在进行的会话,导致 commit 失败。
复盘建议:抓取 logcat -s PackageInstaller PackageManager,按 sessionId 串联五个阶段;再对照 /data/system/packages.xml 与 /data/system/install_sessions.xml,即可确认安装是卡在拷贝、解析还是落库阶段。掌握这条链路后,后续的静默安装、预置应用、增量安装与安装白名单定制,都可以在对应阶段精准切入。