基于 AN16(Android 16)代码
一、整体流程总览
用户点击 APK
│
▼
InstallStart 入口 Activity,权限检查 + 路由分发
│
├── content:// URI
│ ▼
│ InstallStaging 异步拷贝 APK 到 Session 临时目录
│ │
└── package:// URI
▼
PackageInstallerActivity 解析 APK,显示安装确认对话框
│ 用户点击"安装"
▼
InstallInstalling 创建/打开 Session,commit 到 PMS
│ session.commit()
▼
PackageInstallerSession 验证 → seal → verify → install
│ installNonStaged()
▼
InstallingSession 准备安装参数,检查存储空间
│ installPackagesTraced()
▼
InstallPackageHelper PMS 核心五步安装
│
┌───────┴───────┐
▼ ▼
InstallSuccess InstallFailed
显示打开/完成 显示错误信息
二、各阶段详解
阶段一:InstallStart(入口路由)
文件 :packages/PackageInstaller/src/.../InstallStart.java
职责:权限校验 + 路由分发
| 步骤 | 内容 |
|---|---|
| 获取调用者信息 | getLaunchedFromPackage() / getLaunchedFromUid() |
| 权限检查 | 是否持有 INSTALL_PACKAGES,是否声明 REQUEST_INSTALL_PACKAGES |
| 设备策略检查 | DISALLOW_INSTALL_APPS、DISALLOW_INSTALL_UNKNOWN_SOURCES |
| SKG 定制 | 检查 persist.sys.skg.is3rdNotAllowInstall,为 true 则拒绝并 Toast |
| 路由分发 | content:// → InstallStaging,package:// → PackageInstallerActivity |
阶段二:InstallStaging(文件暂存)
文件 :packages/PackageInstaller/src/.../InstallStaging.java
触发条件 :APK 来自 content:// URI(如文件管理器分享)
目的:将文件拷贝到 PackageInstaller 的 Session 目录,防止安装过程中源文件被篡改。
onCreate()
└── 显示拷贝进度对话框
onResume()
└── 创建 PackageInstaller.Session,设置 SessionParams
StagingAsyncTask.doInBackground()
├── installer.openSession(stagedSessionId)
├── getContentResolver().openInputStream(packageUri)
├── session.openWrite() 获取输出流
├── 循环读写(1MB buffer),更新进度
└── session.fsync() 确保落盘
onPostExecute()
└── 跳转 DeleteStagedFileOnResult → PackageInstallerActivity
阶段三:PackageInstallerActivity(安装确认)
文件 :packages/PackageInstaller/src/.../PackageInstallerActivity.java
职责:解析 APK 信息,显示"是否安装"确认界面。
onCreate()
├── 解析 APK(包名、版本、图标、权限列表)
└── processAppSnippet()
onResume() → checkIfAllowedAndInitiateInstall()
├── [SKG] getFileManagerPermission() 对 com.skg.filemanager 特殊处理
├── 受信任来源 → 直接 initiateInstall()
└── 未知来源 → 检查 OP_REQUEST_INSTALL_PACKAGES app op
├── MODE_ALLOWED → initiateInstall()
└── MODE_ERRORED → 弹出"开启未知来源"对话框
用户点击"安装"
├── Session 安装:mInstaller.setPermissionsResult(sessionId, true)
└── 非 Session 安装:startInstall() → 跳转 InstallInstalling
阶段四:InstallInstalling(提交安装)
文件 :packages/PackageInstaller/src/.../InstallInstalling.java
职责:将 Session 提交给 PMS,显示"正在安装..."。
onCreate()
├── 注册 InstallEventReceiver 监听安装结果广播
└── 获取 stagedSessionId,验证 Session 有效性
onResume() → InstallingAsyncTask
└── doInBackground()
└── openSession(mSessionId)
└── onPostExecute()
├── 构建 PendingIntent(接收结果广播)
└── session.commit(pendingIntent.getIntentSender())
↑
进入 Framework 层
阶段五:PackageInstallerSession(Framework 调度)
文件 :services/core/.../pm/PackageInstallerSession.java
职责:验证 APK 完整性、协调安装时序。
commit(statusReceiver)
├── markAsSealed() 标记 Session 不再接受写入
│
├── dispatchStreamValidateAndCommit()
│ └── handleStreamValidateAndCommit()
│ └── validateApkInstallLocked() 校验签名、包名、版本、split
│
└── handleInstall()
├── prepareInheritedFiles() 继承已安装包文件
├── parseApk() 解析 APK
└── verify()
├── runExtractNativeLibraries() 提取 native 库
└── onVerificationComplete()
└── install()
└── installNonStaged()
└── 创建 InstallingSession
阶段六:InstallingSession(参数准备)
文件 :services/core/.../pm/InstallingSession.java
职责:准备安装参数,检查存储空间,进入 PMS 安装。
installStage()
└── mPm.mHandler.post(this::start) 投递到 PMS Handler 线程
start()
├── handleStartCopy(installRequest)
│ ├── [SKG] 检查 is3rdNotAllowInstall,为 true 则拒绝
│ ├── [SKG] 拦截 com.android.vending 升级安装
│ ├── getMinimalPackageInfo() 检查存储空间,确定安装位置
│ └── freeCacheForInstallation() 空间不足时尝试清理缓存
│
└── handleReturnCode(installRequest)
└── processApkInstallRequests()
└── mPm.installPackagesTraced() 进入 PMS 核心
阶段七:InstallPackageHelper(PMS 核心五步安装)
文件 :services/core/.../pm/InstallPackageHelper.java
职责:APK 的实际安装,从文件处理到数据库写入。
installPackagesTraced()
│
├── 第1步 prepareInstallPackages()
│ 解析 APK 完整信息,校验签名兼容性
│ 检查是否允许降级,处理权限和依赖
│
├── 第2步 scanInstallPackages()
│ 扫描 APK,生成 PackageSetting
│ 分配 UID / appId,注册到 PMS 内部数据结构
│
├── 第3步 reconcileInstallPackages()
│ 处理包替换逻辑,签名一致性校验
│ shared user 处理,权限继承
│
├── 第4步 renameAndUpdatePaths()
│ Stage 目录重命名为最终安装目录
│ /data/app/~~random~~/com.example.app-random/
│
└── 第5步 commitInstallPackages()
freezePackageForInstall() 冻结应用
commitPackagesLocked() 写入 packages.xml
executePostCommitStepsLIF()
├── prepareAppDataAfterInstallLIF() 创建应用数据目录
├── performDexOpt() DEX 优化
└── 发送 PACKAGE_ADDED / PACKAGE_REPLACED 广播
阶段八:安装结果回调
InstallPackageHelper 安装完成
↓
InstallingSession.IPackageInstallObserver2.onPackageInstalled()
↓
PackageInstallerSession.dispatchSessionFinished()
↓
sendOnPackageInstalled() → 通过 IntentSender 发送广播
↓
InstallEventReceiver 收到广播
↓
InstallInstalling.launchFinishBasedOnResult()
├── 成功 → InstallSuccess(显示"打开"/"完成")
└── 失败 → InstallFailed(显示错误原因)
三、SKG 定制点汇总
| 位置 | 定制内容 |
|---|---|
InstallStart.java |
persist.sys.skg.is3rdNotAllowInstall 为 true 时,Toast 提示并拒绝安装 |
PackageInstallerActivity.java |
com.skg.filemanager 特殊处理,视为已知来源,跳过未知来源校验 |
PackageInstallerActivity.java |
getFileManagerPermission() 自定义文件管理器安装权限检查 |
InstallingSession.java |
handleStartCopy() 中二次检查 is3rdNotAllowInstall(防绕过) |
InstallingSession.java |
拦截 com.android.vending(Google Play Store)的升级安装 |
四、关键源码文件索引
| 文件 | 路径 | 作用 |
|---|---|---|
| InstallStart | packages/PackageInstaller/src/.../InstallStart.java |
入口,权限检查,路由 |
| InstallStaging | packages/PackageInstaller/src/.../InstallStaging.java |
content:// URI 文件暂存 |
| PackageInstallerActivity | packages/PackageInstaller/src/.../PackageInstallerActivity.java |
APK 解析,安装确认 UI |
| InstallInstalling | packages/PackageInstaller/src/.../InstallInstalling.java |
Session commit,等待结果 |
| InstallSuccess | packages/PackageInstaller/src/.../InstallSuccess.java |
安装成功界面 |
| InstallFailed | packages/PackageInstaller/src/.../InstallFailed.java |
安装失败界面 |
| PackageInstallerSession | services/core/.../pm/PackageInstallerSession.java |
Session 管理,验证,调度 |
| InstallingSession | services/core/.../pm/InstallingSession.java |
安装参数准备,空间检查 |
| InstallPackageHelper | services/core/.../pm/InstallPackageHelper.java |
PMS 核心五步安装 |
| PackageManagerService | services/core/.../pm/PackageManagerService.java |
PMS 入口,转发到 Helper |
五、核心知识点速记
为什么需要 InstallStaging?
content:// URI 属于另一个进程的文件,安装过程中源文件可能被修改,必须先拷贝到 PackageInstaller 控制的 Session 目录确保完整性。
为什么 commit() 后还要等广播?
session.commit() 只是提交任务,PMS 在独立 Handler 线程异步执行,结果通过 PendingIntent 广播异步通知,InstallInstalling 监听广播后再跳转结果页。
packages.xml 是什么?
/data/system/packages.xml,PMS 的包信息数据库,记录所有已安装包的签名、权限、安装路径等,系统启动时从这里恢复包信息。
DEX 优化在哪一步?
在 commitInstallPackages() 的 executePostCommitStepsLIF() 中执行 performDexOpt(),是安装最后阶段,这也是为什么大 APK 安装时间较长。