PackageInstaller 安装 APK之“施工队入场”

116 阅读9分钟

将用 ​​“施工队入场”​​ 的比喻,结合源码为你详细解析 PackageInstaller 安装 APK 的完整流程。故事紧接上一篇(初始化),聚焦用户点击 ​​“安装”​​ 按钮后的关键步骤。


🏗️ ​​故事背景:新建筑(APK)施工流程​

想象你要在小区(Android 设备)里建一栋新楼(安装 APK)。流程如下:

  1. ​你 (用户)​​: 业主,下达建造指令(点击安装按钮)。

  2. ​施工指挥部前台 (PackageInstaller App)​​:

    • PackageInstallerActivity: 展示蓝图、获得你的许可(初始化阶段已完成)。
    • InstallInstalling: 现场项目经理,负责协调施工队入场、材料运输、与总部沟通。
  3. ​施工队与材料 (APK 数据)​​:

    • 需要从仓库(APK 文件位置)运到工地(系统安装目录)。
  4. ​工程总部 (Package Manager Service - PMS)​​:

    • 真正的“建筑管理局”,藏在系统深处 (system_server 进程)。
    • 负责:验收材料、检查资质(签名/权限)、打地基(Dex优化)、注册产权(更新包数据库)。
  5. ​施工许可证 (Session)​​:

    • 一个关键的虚拟许可证,由 PMS 下属的 ​PackageInstallerService​ 签发和管理。
    • 它定义了:要建什么(包名)、在哪建(安装位置)、用什么材料(APK 文件),并提供了安全的 ​​材料运输通道 (Session)​​。
  6. ​工程监理 (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
        ...
    }
    
  • ​比喻:​

    1. 项目经理快速检查蓝图 (parsePackageLite) 是否基本完整(包名、位置、大小)。
    2. 填写详细的施工申请表 (SessionParams)。
    3. 聘请工程监理 (InstallEventReceiver.addObserver),约定完工后必须向他报告。
    4. 将申请表递交给工程总部 (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 { ... } // 运输失败处理
        }
    }
    
  • ​比喻:​

    1. 项目经理拿着许可证 ID (mSessionId),去总部开通了一条专属运输通道 (openSession)。这条通道直通总部内部临时仓库。

    2. 运输队 (InstallingAsyncTask) 在后台默默工作:

      • 打开建筑材料仓库 (FileInputStream)。
      • 打开运输通道的入口 (session.openWrite)。
      • 将建筑材料 (APK 字节) 分块 (byte[] buffer) 装上卡车,源源不断地运入通道 (out.write)。
      • 确保所有材料安全送达 (fsync)。
    3. 材料运输完毕!项目经理 (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)!
    }
    
  • ​比喻:​

    1. 项目经理 (InstallInstalling) 通过许可证 (Session) 的 commit 方法,正式递交盖好章的验收申请单(内含监理回调地址 PendingIntent)。

    2. 申请单通过 ​​内部专用通道 (Binder IPC IPackageInstallerSession)​​ 送达工程总部 (PackageInstallerService / PackageInstallerSession)。

    3. 总部文书 (PackageInstallerSession) 收到申请:

      • 将项目经理附带的监理地址 (PendingIntent) 登记为内部监工 (PackageInstallObserverAdapter)。
      • 立刻向总部的施工调度中心 (Handler) 发任务单 (MSG_COMMIT),任务单上写着监工的联系方式 (observer Binder)。
    4. 调度中心 (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) 收到广播,解析出结果 (statusCodemessage),然后调用绑定好的 launchFinishBasedOnResult 方法。
    • InstallInstalling 根据结果展示成功或失败界面给用户。
  • ​比喻:​​ 项目经理在工地办公室喝茶等待。监理 (InstallEventReceiver) 收到总部 (PMS) 发来的电报(广播),上面写着“验收成功 ✅”或“验收失败 ❌,原因:...”。项目经理根据电报内容,向你(用户)展示最终结果页面。


💎 ​​技术要点总结​

  1. Session 机制是核心桥梁:​

    • 由 PackageInstallerService (PMS 下属) 管理。
    • createSession: 申请许可,定义参数。
    • openSession + openWrite/read:提供安全的 ​​跨进程文件流传输通道​​,避免直接文件路径暴露(安全)。
    • commit: 触发 PMS 接管后续安装流程,传递结果回调 (IntentSender)。
  2. InstallEventReceiver 处理结果回调:​

    • 基于 BroadcastReceiver 和 PendingIntent 实现跨进程结果通知。
    • 将 PMS 的安装结果传递回 UI 层 (InstallInstalling),用于更新界面。
  3. ​轻量解析 vs 重量解析:​

    • parsePackageLite (InstallInstalling):快速读取 APK 基础信息 (包名、大小、位置),用于 Session 创建。
    • PMS 内部会进行 ​​完整解析 (PackageParser.parsePackage)​​ 和深度扫描(权限、组件、签名等)。
  4. ​异步写入 (InstallingAsyncTask):​

    • 文件 IO 操作耗时,必须在后台线程执行,避免阻塞 UI。
  5. ​职责清晰分离:​

    • ​PackageInstaller App:​​ UI 交互、Session 申请与管理、文件传输、结果展示。
    • ​PackageInstallerService / Session:​​ Session 管理、文件传输通道提供、与 PMS 接口。
    • ​PMS:​​ 核心安装逻辑 (文件操作、包扫描、Dex 优化、数据库更新、权限处理)。
  6. ​安全设计:​

    • 内容 URI (content://) 避免文件路径暴露。
    • Session 提供安全的文件传输通道。
    • 权限检查贯穿始终 (InstallStartPackageInstallerActivity, PMS)。

🏁 流程全景图

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 内部安装)将是真正的“建筑施工”核心。