将用 “施工队入场” 的比喻,结合源码为你详细解析 PackageInstaller 安装 APK 的完整流程。故事紧接上一篇(初始化),聚焦用户点击 “安装” 按钮后的关键步骤。
🏗️ 故事背景:新建筑(APK)施工流程
想象你要在小区(Android 设备)里建一栋新楼(安装 APK)。流程如下:
-
你 (用户): 业主,下达建造指令(点击安装按钮)。
-
施工指挥部前台 (PackageInstaller App):
PackageInstallerActivity: 展示蓝图、获得你的许可(初始化阶段已完成)。InstallInstalling: 现场项目经理,负责协调施工队入场、材料运输、与总部沟通。
-
施工队与材料 (APK 数据):
- 需要从仓库(APK 文件位置)运到工地(系统安装目录)。
-
工程总部 (Package Manager Service - PMS):
- 真正的“建筑管理局”,藏在系统深处 (
system_server进程)。 - 负责:验收材料、检查资质(签名/权限)、打地基(Dex优化)、注册产权(更新包数据库)。
- 真正的“建筑管理局”,藏在系统深处 (
-
施工许可证 (Session):
- 一个关键的虚拟许可证,由 PMS 下属的
PackageInstallerService 签发和管理。 - 它定义了:要建什么(包名)、在哪建(安装位置)、用什么材料(APK 文件),并提供了安全的 材料运输通道 (Session)。
- 一个关键的虚拟许可证,由 PMS 下属的
-
工程监理 (InstallEventReceiver):
- 负责监听施工进度(安装成功/失败),并向项目经理 (
InstallInstalling) 报告结果。
- 负责监听施工进度(安装成功/失败),并向项目经理 (
🚧 核心步骤详解 (源码映射)
第 1 步:业主下达开工令 (用户点击安装)
-
场景: 你在
PackageInstallerActivity界面点击了 “安装” 按钮。 -
代码 (
PackageInstallerActivity.onClick):java Copy // packages/apps/PackageInstaller/.../PackageInstallerActivity.java public void onClick(View v) { if (v == mOk) { // 用户点击了"确定"按钮 ... // 省略一些检查 startInstall(); // 关键!启动安装流程 } else if (v == mCancel) { ... } // 取消安装 } private void startInstall() { Intent newIntent = new Intent(); newIntent.setData(mPackageURI); // 携带蓝图地址 (APK Uri) newIntent.setClass(this, InstallInstalling.class); // 派项目经理上场! ... // 设置一些额外信息 (来源URI等) startActivity(newIntent); // 启动 InstallInstalling finish(); // PackageInstallerActivity 退场 } -
比喻: 你签好施工合同(点击安装),前台 (
PackageInstallerActivity) 立刻联系现场项目经理 (InstallInstalling) 并移交蓝图 (APK Uri),然后自己下班。
第 2 步:项目经理进场,申请施工许可证 (InstallInstalling.onCreate)
-
场景: 项目经理
InstallInstalling到达工地现场(其onCreate被调用)。 -
关键任务: 向工程总部 (PMS) 申请施工许可证 (
Session) 并 注册监理 (InstallEventReceiver)。 -
代码 (
InstallInstalling.onCreate):java Copy // packages/apps/PackageInstaller/.../InstallInstalling.java @Override protected void onCreate(@Nullable Bundle savedInstanceState) { ... mPackageURI = getIntent().getData(); // 拿到蓝图地址 ... // 1. 轻量级验货 (检查蓝图基本合规性) File file = new File(mPackageURI.getPath()); PackageParser.PackageLite pkgLite = PackageParser.parsePackageLite(file, 0); // 快速解析APK头信息 // 2. 填写施工许可证申请表 (SessionParams) PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); // 模式:全新安装 params.setAppPackageName(pkgLite.packageName); // 包名 = 楼宇名称 params.setInstallLocation(pkgLite.installLocation); // 安装位置 = 小区内位置 params.setSize(PackageHelper.calculateInstalledSize(pkgLite, ...)); // 预估占地面积 // 3. 向工程总部注册监理,接收施工结果报告 mInstallId = InstallEventReceiver.addObserver(this, ... , this::launchFinishBasedOnResult); // ^^ 注册监理回调 launchFinishBasedOnResult (无论成功失败都会调用) // 4. 正式向工程总部申请施工许可证 (Session)! PackageInstaller installer = getPackageManager().getPackageInstaller(); // 连接总部接口 mSessionId = installer.createSession(params); // 提交申请表,拿到许可证ID ... } -
比喻:
- 项目经理快速检查蓝图 (
parsePackageLite) 是否基本完整(包名、位置、大小)。 - 填写详细的施工申请表 (
SessionParams)。 - 聘请工程监理 (
InstallEventReceiver.addObserver),约定完工后必须向他报告。 - 将申请表递交给工程总部 (
PackageInstallerService),总部审核后签发一张带有唯一 ID (mSessionId) 的 虚拟施工许可证 (Session)。这张许可证本身,就包含了安全的材料运输通道!
- 项目经理快速检查蓝图 (
第 3 步:运输建筑材料 (写入 APK 数据到 Session)
-
场景: 项目经理 (
InstallInstalling) 拿到许可证 (Session) 后,开始组织运输队将建筑材料 (APK 文件内容) 通过许可证指定的安全通道运送到总部指定的临时仓库。 -
代码 (
InstallInstalling.onResume+InstallingAsyncTask):java Copy // packages/apps/PackageInstaller/.../InstallInstalling.java @Override protected void onResume() { ... // 1. 检查许可证状态 (确保有效且未开工) PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId); if (sessionInfo != null && !sessionInfo.isActive()) { // 2. 启动运输任务! mInstallingTask = new InstallingAsyncTask(); mInstallingTask.execute(); // 在后台线程执行运输 } ... } private final class InstallingAsyncTask extends AsyncTask<Void, Void, PackageInstaller.Session> { @Override protected PackageInstaller.Session doInBackground(Void... params) { // 1. 根据许可证ID,获取运输通道 (打开Session) PackageInstaller.Session session = getPackageManager().getPackageInstaller().openSession(mSessionId); // 2. 打开蓝图文件 (APK) try (InputStream in = new FileInputStream(new File(mPackageURI.getPath()))) { // 3. 通过运输通道写入数据 (核心!) OutputStream out = session.openWrite("PackageInstaller", 0, -1); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = in.read(buffer)) >= 0) { // 分块读取APK out.write(buffer, 0, bytesRead); // 分块写入Session通道 } session.fsync(out); // 确保数据落盘 out.close(); } catch (...) { ... return null; } return session; // 运输完成! } @Override protected void onPostExecute(PackageInstaller.Session session) { if (session != null) { // 4. 运输完成!准备提交验收申请 ... // 准备验收报告单 (PendingIntent) session.commit(pendingIntent.getIntentSender()); // 关键!提交验收申请 } else { ... } // 运输失败处理 } } -
比喻:
-
项目经理拿着许可证 ID (
mSessionId),去总部开通了一条专属运输通道 (openSession)。这条通道直通总部内部临时仓库。 -
运输队 (
InstallingAsyncTask) 在后台默默工作:- 打开建筑材料仓库 (
FileInputStream)。 - 打开运输通道的入口 (
session.openWrite)。 - 将建筑材料 (APK 字节) 分块 (
byte[] buffer) 装上卡车,源源不断地运入通道 (out.write)。 - 确保所有材料安全送达 (
fsync)。
- 打开建筑材料仓库 (
-
材料运输完毕!项目经理 (
onPostExecute) 准备正式的 验收申请单 (PendingIntent),并通过许可证 (session) 正式向总部提交验收申请 (commit)。这个申请单上写着:“材料已送达,请监理 (InstallEventReceiver) 在验收完成后通知我”。
-
第 4 步:提交验收申请,总部接管 (Session.commit -> PMS)
-
场景: 项目经理 (
InstallInstalling) 通过许可证 (Session) 提交验收申请 (commit)。申请单被送到工程总部 (PMS),总部开始接管后续所有复杂工作。 -
代码路径 (跨进程通信):
java Copy // 1. InstallInstalling (App进程): session.commit(pendingIntent.getIntentSender()); // 提交申请 // 2. 进入 framework (PackageInstallerService / PackageInstallerSession): // frameworks/base/core/java/android/content/pm/PackageInstaller.java (Session) public void commit(@NonNull IntentSender statusReceiver) { mSession.commit(statusReceiver); // mSession 是 IPackageInstallerSession (Binder代理) } // 3. 进入 system_server 进程 (PackageInstallerSession): // frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java @Override public void commit(IntentSender statusReceiver) { ... // 封装监理回调 (将 App 的 PendingIntent 转换为 PMS 内部可用的观察者) final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter( mContext, statusReceiver, sessionId, ...); // 发送内部消息:开始处理安装 mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget(); } // 4. Handler 处理消息 (仍在 PackageInstallerSession) private final Handler.Callback mHandlerCallback = new Handler.Callback() { public boolean handleMessage(Message msg) { if (msg.what == MSG_COMMIT) { IPackageInstallObserver2 observer = (IPackageInstallObserver2) msg.obj; // 拿到Binder观察者 try { commitLocked(...); // 核心!调用真正的安装逻辑 } catch (Exception e) { // 失败:通过观察者回调通知失败结果 observer.onPackageInstalled(..., e.error, e.getMessage(), ...); } } return true; } }; // 5. commitLocked 最终调用 PMS 的核心安装入口 private void commitLocked(...) throws PackageManagerException { ... mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params, ...); // ^^ mPm 就是 PackageManagerService (PMS)! } -
比喻:
-
项目经理 (
InstallInstalling) 通过许可证 (Session) 的commit方法,正式递交盖好章的验收申请单(内含监理回调地址PendingIntent)。 -
申请单通过 内部专用通道 (Binder IPC
IPackageInstallerSession) 送达工程总部 (PackageInstallerService/PackageInstallerSession)。 -
总部文书 (
PackageInstallerSession) 收到申请:- 将项目经理附带的监理地址 (
PendingIntent) 登记为内部监工 (PackageInstallObserverAdapter)。 - 立刻向总部的施工调度中心 (
Handler) 发任务单 (MSG_COMMIT),任务单上写着监工的联系方式 (observer Binder)。
- 将项目经理附带的监理地址 (
-
调度中心 (
Handler) 处理任务:- 调用
commitLocked开始真正的施工。 - 最关键一步:将任务移交给真正的“建筑总局” (
PMS),调用mPm.installStage(...)。从此,安装的核心流程完全由PMS接管! - 如果移交过程本身出错(比如材料丢失),会直接通过监工 (
observer) 向项目经理报告失败。
- 调用
-
第 5 步:项目经理等待验收报告 (InstallInstalling 等待回调)
-
场景: 项目经理 (
InstallInstalling) 在提交验收申请 (commit) 后,他的工作就基本完成了。他只需要留在工地,等待工程监理 (InstallEventReceiver) 发来的最终验收报告。 -
代码 (
launchFinishBasedOnResult):java Copy // packages/apps/PackageInstaller/.../InstallInstalling.java private void launchFinishBasedOnResult(int statusCode, String message) { if (statusCode == PackageManager.INSTALL_SUCCEEDED) { launchSuccess(); // 显示安装成功界面 } else { launchFailure(statusCode, message); // 显示安装失败界面及原因 } } -
回调机制:
- 当
PMS内部完成复杂的安装工作(包括拷贝、扫描、优化、注册等)后,无论成功或失败,最终会触发之前注册的监理回调。 - 这个回调会发送一个广播 (
BROADCAST_ACTION)。 InstallEventReceiver(一个BroadcastReceiver) 收到广播,解析出结果 (statusCode,message),然后调用绑定好的launchFinishBasedOnResult方法。InstallInstalling根据结果展示成功或失败界面给用户。
- 当
-
比喻: 项目经理在工地办公室喝茶等待。监理 (
InstallEventReceiver) 收到总部 (PMS) 发来的电报(广播),上面写着“验收成功 ✅”或“验收失败 ❌,原因:...”。项目经理根据电报内容,向你(用户)展示最终结果页面。
💎 技术要点总结
-
Session机制是核心桥梁:- 由
PackageInstallerService(PMS 下属) 管理。 createSession: 申请许可,定义参数。openSession+openWrite/read:提供安全的 跨进程文件流传输通道,避免直接文件路径暴露(安全)。commit: 触发 PMS 接管后续安装流程,传递结果回调 (IntentSender)。
- 由
-
InstallEventReceiver处理结果回调:- 基于
BroadcastReceiver和PendingIntent实现跨进程结果通知。 - 将 PMS 的安装结果传递回 UI 层 (
InstallInstalling),用于更新界面。
- 基于
-
轻量解析 vs 重量解析:
parsePackageLite(InstallInstalling):快速读取 APK 基础信息 (包名、大小、位置),用于Session创建。- PMS 内部会进行 完整解析 (
PackageParser.parsePackage) 和深度扫描(权限、组件、签名等)。
-
异步写入 (
InstallingAsyncTask):- 文件 IO 操作耗时,必须在后台线程执行,避免阻塞 UI。
-
职责清晰分离:
- PackageInstaller App: UI 交互、Session 申请与管理、文件传输、结果展示。
- PackageInstallerService / Session: Session 管理、文件传输通道提供、与 PMS 接口。
- PMS: 核心安装逻辑 (文件操作、包扫描、Dex 优化、数据库更新、权限处理)。
-
安全设计:
- 内容 URI (
content://) 避免文件路径暴露。 Session提供安全的文件传输通道。- 权限检查贯穿始终 (
InstallStart,PackageInstallerActivity, PMS)。
- 内容 URI (
🏁 流程全景图
Copy
用户点击安装 (PackageInstallerActivity.onClick)
|
V
启动 InstallInstalling (startActivity)
|
V
InstallInstalling.onCreate:
|- 解析 APK Lite (parsePackageLite)
|- 创建 SessionParams
|- 注册结果监听器 (InstallEventReceiver.addObserver)
|- 申请 Session (installer.createSession -> PMS)
|
V
InstallInstalling.onResume:
|- 检查 Session 状态
|- 启动 InstallingAsyncTask (后台)
|
| (AsyncTask.doInBackground)
V
|- 打开 Session (installer.openSession)
|- 打开 APK 文件流 (FileInputStream)
|- 打开 Session 输出流 (session.openWrite)
|- 分块写入 APK 数据
|
V
InstallingAsyncTask.onPostExecute:
|- 提交 Session (session.commit(IntentSender))
|
| (Binder IPC)
V
PackageInstallerSession.commit (system_server):
|- 封装观察者 (PackageInstallObserverAdapter)
|- Handler 发送 MSG_COMMIT
|
V
PackageInstallerSession.mHandlerCallback:
|- 调用 commitLocked()
|
V
commitLocked() 内部:
|- 最终调用 PMS.installStage() // 核心安装开始!
|
| (PMS 内部进行复杂安装操作...)
|
V
安装完成 (无论成败) -> 触发 Observer 回调
|
| (Binder Callback)
V
PackageInstallObserverAdapter (system_server)
|- 发送广播 (BROADCAST_ACTION)
|
| (Broadcast)
V
InstallEventReceiver.onReceive (App 进程)
|- 调用 launchFinishBasedOnResult(status, message)
|
V
InstallInstalling.launchFinishBasedOnResult:
|- 显示成功/失败界面给用户
通过这个“施工队入场”的故事和详细的源码解析,你应该清晰地看到了用户点击安装按钮后,APK 数据如何安全传输、Session 如何协调、以及控制权如何移交到 PMS 的全过程。这是 Android 包管理中承上启下的关键环节!下一阶段(PMS 内部安装)将是真正的“建筑施工”核心。