安装全景
本文承接 解析错误处理、Split APK 解析 和 PMS 架构全景。它是安装模块的导航篇:只回答不同入口如何进入 system_server、哪些对象负责承接请求、安装阶段在哪里汇合,以及失败后由谁清理和通知。
本文不替代后续专题。InstallPackageHelper 的准备检查、InstallingSession 的会话生命周期、APK 拷贝、dexopt、安装后注册和 staged install 分别在 PMS044 以后展开。这里保留足够源码,让读者能从入口跳到真实调用点,而不是把安装流程压缩成一张抽象流程图。
1. 入口地图
1.1 三条路径
adb 和应用商店都走 PackageInstaller 会话;系统启动扫描不创建用户安装会话,而是直接调用 InstallPackageHelper.initPackageTracedLI()。三者最后都在包扫描、Settings 更新和组件注册等 PMS 内部状态上汇合,但入口对象和安装 flags 不同。
1.2 安装模块对象
| 对象 | owner | 主要状态 |
|---|---|---|
PackageManagerShellCommand | system_server shell 命令 | 命令参数、目标用户、文件列表 |
PackageInstallerService | PMS | session 集合、权限与 flags 归一化 |
PackageInstallerSession | 安装会话 | stage 目录、写入/封存、回调、子会话 |
InstallingSession | PMS 安装线程 | origin、flags、PackageLite、observer |
InstallRequest | 一次具体包请求 | prepare/scan/reconcile/commit 结果 |
InstallPackageHelper | PMS | 安装阶段编排和最终提交 |
PackageSetting | PMS Settings | 已安装包状态、per-user 状态和瞬态库信息 |
2. Shell 入口
2.1 pm install 入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
private int runInstall() throws RemoteException {
return doRunInstall(makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS));
}
private int doRunInstall(final InstallParams params) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
if (!SystemProperties.getBoolean("sys.boot_completed", false)) {
pw.println("Error: device is still booting.");
return 1;
}
final boolean isStreaming = params.sessionParams.dataLoaderParams != null;
final boolean isApex =
(params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
final ArrayList<String> args = getRemainingArgs();
final boolean fromStdIn = args.isEmpty() || STDIN_PATH.equals(args.get(0));
final boolean hasSplits = args.size() > 1;
if (fromStdIn && params.sessionParams.sizeBytes == -1) {
pw.println("Error: must either specify a package size or an APK file");
return 1;
}
if (isApex && hasSplits) {
pw.println("Error: can't specify SPLIT(s) for APEX");
return 1;
}shell 命令在创建 session 前先做命令级约束:设备必须完成启动,stdin 必须提供大小,APEX 不能带 split。这里的错误不会进入 InstallPackageHelper,因为尚未创建安装请求。
2.2 创建、写入与提交
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
boolean abandonSession = true;
try {
if (isStreaming) {
if (doAddFiles(sessionId, args, params.sessionParams.sizeBytes,
isApex, installArchived) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
} else {
if (doWriteSplits(sessionId, args, params.sessionParams.sizeBytes,
isApex) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
}
if (doCommitSession(sessionId, false /*logSuccess*/)
!= PackageInstaller.STATUS_SUCCESS) {
return 1;
}
abandonSession = false;
pw.println("Success");
return 0;
} finally {
if (abandonSession) {
try {
doAbandonSession(sessionId, false /*logSuccess*/);
} catch (Exception ignore) {
}
}
}shell 入口把“创建 session、写入 stage、commit”视为一个小事务:任何写入或提交前失败都会 abandon session;只有 commit 调用成功才取消这个清理标记。这里的 Success 表示命令已提交,不等于所有后续 dexopt/commit 阶段都已完成,staged session 还会有单独等待逻辑。
2.3 Shell 调用
private int doCreateSession(SessionParams params, String installerPackageName, int userId)
throws RemoteException {
if (userId == UserHandle.USER_ALL) {
params.installFlags |= PackageManager.INSTALL_ALL_USERS;
}
final int translatedUserId =
translateUserId(userId, UserHandle.USER_SYSTEM, "doCreateSession");
return mInterface.getPackageInstaller().createSession(
params, installerPackageName, null /*installerAttributionTag*/,
translatedUserId);
}PackageManagerShellCommand 自己不创建 PackageInstallerSession 实例,而是通过 IPackageInstallerService Binder 接口调用 system_server 中的 PackageInstallerService。目标用户在这里翻译,USER_ALL 还会先变成 INSTALL_ALL_USERS flag。
3. Installer 入口
3.1 Session 边界
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.java
@Override
public int createSession(SessionParams params, String installerPackageName,
String callingAttributionTag, int userId) {
try {
if (params.dataLoaderParams != null
&& mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("You need USE_INSTALLER_V2 permission");
}
// Draft sessions cannot be created through the public API.
params.installFlags &= ~PackageManager.INSTALL_UNARCHIVE_DRAFT;
return createSessionInternal(params, installerPackageName,
callingAttributionTag, Binder.getCallingUid(), userId);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}应用商店和系统安装器都从这个 Binder 边界进入。data loader 需要额外权限;公开 API 不允许创建 draft session;I/O 异常在服务边界转换为运行时异常。
3.2 Caller 与 flags
int createSessionInternal(SessionParams params, String installerPackageName,
String installerAttributionTag, int callingUid, int userId) throws IOException {
final Computer snapshot = mPm.snapshotComputer();
snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "createSession");
if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
throw new SecurityException("User restriction prevents installing");
}
final String requestedInstallerPackageName = params.installerPackageName != null
? params.installerPackageName : installerPackageName;
if (PackageManagerServiceUtils.isRootOrShell(callingUid)
|| PackageInstallerSession.isSystemDataLoaderInstallation(params)
|| PackageManagerServiceUtils.isAdoptedShell(callingUid, mContext)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
// adb may override installingPackageName, but not initiatingPackageName.
installerPackageName = SHELL_PACKAGE_NAME;
} else {
if (callingUid != SYSTEM_UID) {
mAppOps.checkPackage(callingUid, installerPackageName);
}
if (!TextUtils.equals(requestedInstallerPackageName, installerPackageName)
&& mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
!= PackageManager.PERMISSION_GRANTED) {
mAppOps.checkPackage(callingUid, requestedInstallerPackageName);
}
params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
}这个函数不是简单的 session 工厂,而是入口身份和 flags 的规范化点:shell/root 被标记为 ADB 来源;普通调用者必须通过 AppOps 验证包名,不能保留 ADB 或全用户 flag,并默认带上 replace-existing。安装器传入的 flags 到这里可能已经被清理或补充。
3.3 注册 session
final int sessionId = allocateSessionIdLocked();
final File stageDir = buildSessionDir(sessionId, params);
final PackageInstallerSession session = new PackageInstallerSession(
mInternalCallback, mContext, mPm, mInstallThread.getLooper(),
mStagingManager, sessionId, userId, callingUid, installSource,
params, stageDir, /*isApex=*/ false);
synchronized (mSessions) {
mSessions.put(sessionId, session);
}
mSessionsChanged = true;
writeSessionsAsync();
return sessionId;session 的 owner 是 PackageInstallerService,stage 目录和 session 元数据会先登记,随后异步持久化。此时 APK 可能还没有写入,InstallPackageHelper 尚未参与。
4. Session 与任务
4.1 commit 封存
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java
@Override
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
assertNotChild("commit");
if (!markAsSealed(statusReceiver, forTransfer)) {
return;
}
if (isMultiPackage()) {
synchronized (mLock) {
boolean sealFailed = false;
for (int i = mChildSessions.size() - 1; i >= 0; --i) {
if (!mChildSessions.valueAt(i).markAsSealed(null, forTransfer)) {
sealFailed = true;
}
}
if (sealFailed) {
return;
}
}
}
dispatchSessionSealed();
}commit() 的第一件事是把 session 变成 sealed,multi-package 还要封存所有 child。封存后调用方不能继续修改 stage;真正的流校验、PackageLite 读取和 InstallingSession 创建由后续 handler 消息驱动。
4.2 sealed 消息链
private void dispatchSessionSealed() {
mHandler.obtainMessage(MSG_ON_SESSION_SEALED).sendToTarget();
}
private void handleSessionSealed() {
assertSealed("dispatchSessionSealed");
// Persist sealed state before any hard links can be created.
mCallback.onSessionSealedBlocking(this);
dispatchStreamValidateAndCommit();
}封存状态先同步持久化,再进入流校验和提交。这里的“提交”仍是 session 层提交,不是 InstallPackageHelper.commitPackagesLocked();两者之间还隔着 package 解析、复制和 reconcile。
4.3 创建安装任务
private InstallingSession createInstallingSession(
CompletableFuture<InstallResult> future) throws PackageManagerException {
synchronized (mLock) {
if (!mSealed) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Session not sealed");
}
markStageDirInUseLocked();
}
final UserHandle user = (params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0
? UserHandle.ALL : new UserHandle(userId);
if (!isMultiPackage() && !isApexSession()) {
synchronized (mLock) {
mPackageLite = getOrParsePackageLiteLocked(stageDir, /*flags=*/ 0);
}
}
synchronized (mLock) {
return new InstallingSession(sessionId, stageDir, localObserver, params,
mInstallSource, user, mSigningDetails, mInstallerUid, mPackageLite,
mPreVerifiedDomains, mPm, mHasAppMetadataFile,
mDependencyInstallerEnabled.get(), mMissingSharedLibraryCount.get(),
mDeveloperVerificationStatusInternal, mWarnings);
}
}PackageInstallerSession 在 sealed 后把 stage 目录标记为使用中,并将 PackageLite、用户、签名、预验证域名和 flags 封装进 InstallingSession。PackageLite 是安装任务的轻量输入,完整 ParsedPackage 仍由后面的 parser 创建。
5. 安装任务
5.1 承接状态
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallingSession.java
final OriginInfo mOriginInfo;
final IPackageInstallObserver2 mObserver;
int mInstallFlags;
final InstallSource mInstallSource;
final UserHandle mUser;
final PackageManagerService mPm;
final boolean mIsInherit;
final int mSessionId;
final PackageLite mPackageLite;
@Nullable final DomainSet mPreVerifiedDomains;
final List<String> mWarnings;InstallingSession 是 session 层和 PMS 安装编排层之间的适配对象。它不保存 ParsedPackage,而是保存 stage/origin、安装 flags、用户、PackageLite、observer 和安装策略参数。
5.2 单包与多包调度
public void installStage() {
setTraceMethod("installStage").setTraceCookie(System.identityHashCode(this));
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "installStage",
System.identityHashCode(this));
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(this));
mPm.mHandler.post(this::start);
}
public void installStage(List<InstallingSession> children)
throws PackageManagerException {
final MultiPackageInstallingSession installingSession =
new MultiPackageInstallingSession(getUser(), children, mPm);
setTraceMethod("installStageMultiPackage")
.setTraceCookie(System.identityHashCode(installingSession));
mPm.mHandler.post(installingSession::start);
}
private void start() {
InstallRequest installRequest = new InstallRequest(this);
handleStartCopy(installRequest);
handleReturnCode(installRequest);
}单包通过 handler 异步执行 start();multi-package 把 child InstallingSession 交给 MultiPackageInstallingSession,等待所有 child 的复制结果再继续。安装原子性从这里开始表现为“多个 InstallRequest 一起进入后续阶段”。
5.3 复制结果进入 PMS
private void handleReturnCode(InstallRequest installRequest) {
processPendingInstall(installRequest);
}
private void processPendingInstall(InstallRequest installRequest) {
if (mRet == PackageManager.INSTALL_SUCCEEDED) {
mRet = copyApk(installRequest);
}
installRequest.setReturnCode(mRet);
if (mParentInstallingSession != null) {
mParentInstallingSession.tryProcessInstallRequest(installRequest);
} else {
mPm.mHandler.post(() -> processInstallRequests(
mRet == PackageManager.INSTALL_SUCCEEDED,
Collections.singletonList(installRequest)));
}
}stage 处理成功后才调用 copyApk();复制失败的 InstallRequest 不会进入正常安装阶段。单包直接 post 到 PMS handler,多包则先回传 parent,等所有 child 状态齐全后再统一处理。
6. 统一安装管道
6.1 安装编排
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
void installPackagesTraced(List<InstallRequest> requests, MoveInfo moveInfo) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages");
boolean pendingForDexopt = false;
boolean success = false;
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
final long acquireTime = acquireWakeLock(requests.size());
try {
CriticalEventLog.getInstance().logInstallPackagesStarted();
if (prepareInstallPackages(requests)
&& scanInstallPackages(requests, createdAppId, versionInfos)) {
List<ReconciledPackage> reconciledPackages =
reconcileInstallPackages(requests, versionInfos);
if (reconciledPackages == null) {
return;
}
if (renameAndUpdatePaths(requests)) {
pendingForDexopt = true;
final Runnable actionsAfterDexopt = () -> doPostDexopt(
reconciledPackages, requests, createdAppId,
moveInfo, acquireTime);
prepPerformDexoptIfNeeded(reconciledPackages, actionsAfterDexopt);
}
}
} finally {
if (!pendingForDexopt) {
completeInstallProcess(requests, createdAppId, success);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
doPostInstall(requests, moveInfo);
releaseWakeLock(acquireTime, requests.size());
}
}
}这段代码是安装模块真正的汇合点。它按 prepare -> scan -> reconcile -> rename/path -> dexopt -> commit -> post-install 编排;任一前置阶段失败都会跳过后续阶段。pendingForDexopt 用来表示流程已交给异步 dexopt,避免 finally 提前完成安装。
6.2 四阶段的 owner
| 阶段 | 函数 | 主要状态变化 |
|---|---|---|
| Prepare | prepareInstallPackages | origin、包信息、安装位置和前置检查结果 |
| Scan | scanInstallPackages | ParsedPackage 进入 ScanResult,注册 appId 候选 |
| Reconcile | reconcileInstallPackages | 与现有包、Settings、共享库和签名状态比较 |
| Commit | commitPackagesLocked | 在 PMS lock 下修改系统包状态 |
源码注释特别强调:Commit 是安装流程中允许修改系统状态的阶段,且可预测错误应在此前确定。这个约束解释了为什么 scan/reconcile 要先完成,不能把失败留到提交中再猜测恢复方式。
6.3 前置阶段失败
private boolean prepareInstallPackages(List<InstallRequest> requests) {
for (InstallRequest request : requests) {
try {
request.onPrepareStarted();
preparePackage(request);
} catch (PrepareFailure prepareFailure) {
request.setError(prepareFailure.error, prepareFailure.getMessage());
request.setOriginPackage(prepareFailure.mConflictingPackage);
request.setOriginPermission(prepareFailure.mConflictingPermission);
return false;
} finally {
request.onPrepareFinished();
}
}
return true;
}
private boolean scanInstallPackages(List<InstallRequest> requests,
Map<String, Boolean> createdAppId, Map<String, Settings.VersionInfo> versionInfos) {
final Set<String> scannedPackages = new ArraySet<>(requests.size());
for (InstallRequest request : requests) {
final ParsedPackage packageToScan = request.getParsedPackage();
if (packageToScan == null) {
request.setError(INSTALL_FAILED_SESSION_INVALID,
"Failed to obtain package to scan");
return false;
}
try {
request.onScanStarted();
final ScanResult scanResult = scanPackageTraced(packageToScan,
request.getParseFlags(), request.getScanFlags(),
System.currentTimeMillis(), request.getUser(),
request.getAbiOverride(), request.getInstallSource());
request.setScanResult(scanResult);
request.onScanFinished();
if (!scannedPackages.add(packageToScan.getPackageName())) {
request.setError(PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
"Duplicate package in multi-package install request.");
return false;
}
} catch (PackageManagerException e) {
request.setError("Scanning Failed.", e);
return false;
}
}
return true;
}Prepare 失败会写入 InstallRequest 并停止整批处理;Scan 要求 ParsedPackage 非 null,并拒绝 multi-package 中的重复 package name。失败结果仍保留在 request 中,供清理和 observer 通知使用。
7. 系统启动扫描
7.1 与用户安装分开
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
@GuardedBy("mPm.mInstallLock")
public AndroidPackage initPackageTracedLI(File scanFile,
final int parseFlags, int scanFlags) throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER,
"scanPackage [" + scanFile.toString() + "]");
try {
return initPackageLI(scanFile, parseFlags, scanFlags);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}启动扫描的 owner 也是 InstallPackageHelper,但入口是 initPackageTracedLI(),由 InitAppsHelper 在 system_server 启动阶段调用。它没有 PackageInstallerSession、stage 写入和用户 observer;它直接把系统分区 APK 解析/扫描进 PMS 状态。
7.2 两类扫描边界
用户安装和启动扫描最终都修改 PMS 的包状态,但前者必须经过 session 封存和 InstallRequest 阶段编排,后者直接以扫描文件为输入。不要用 PackageInstallerSession 去解释启动扫描,也不要把启动扫描称为一次普通用户安装。
8. 提交、清理与回调
8.1 Commit 写状态
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
private boolean commitInstallPackages(List<ReconciledPackage> reconciledPackages) {
try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
for (ReconciledPackage reconciledPkg : reconciledPackages) {
InstallRequest request = reconciledPkg.mInstallRequest;
String packageName = request.getParsedPackage().getPackageName();
PackageFreezer freezer = freezePackageForInstall(packageName,
UserHandle.USER_ALL, request.getInstallFlags(),
"installPackageLI", ApplicationExitInfo.REASON_PACKAGE_UPDATED,
request);
request.setFreezer(freezer);
}
synchronized (mPm.mLock) {
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "commitPackages");
commitPackagesLocked(reconciledPackages, mPm.mUserManager.getUserIds());
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
executePostCommitStepsLIF(reconciledPackages);
}
return true;
}提交前冻结包,随后在 mInstallLock 和 mPm.mLock 保护下调用 commitPackagesLocked()。系统状态真正改变的时机在这里;前面的 stage、parse、scan 和 reconcile 都只是准备或验证。
8.2 失败安装清理
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallingSession.java
private void processApkInstallRequests(boolean success,
List<InstallRequest> installRequests) {
if (!success) {
for (InstallRequest request : installRequests) {
if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
cleanUpForFailedInstall(request);
}
}
for (InstallRequest request : installRequests) {
mPm.restoreAndPostInstall(request);
}
} else {
mPm.installPackagesTraced(installRequests, mMoveInfo);
}
}
private void cleanUpForFailedInstall(InstallRequest request) {
if (request.isInstallMove()) {
mPm.cleanUpForMoveInstall(request.getMoveToUuid(),
request.getMovePackageName(), request.getMoveFromCodePath());
} else {
mPm.removeCodePath(request.getCodeFile());
}
}复制/准备阶段失败时,InstallingSession 负责删除 code path 或清理 move install,再调用 restoreAndPostInstall();只有所有前置步骤成功才把 request 交给 InstallPackageHelper。这与 InstallPackageHelper 在 commit 后的 post-install 清理是两个不同阶段。
8.3 最终通知
static void onInstallComplete(int returnCode, Context context, IntentSender target) {
Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
PackageManager.installStatusToPublicStatus(returnCode));
try {
target.sendIntent(context, 0, fillIn, null, null);
} catch (IntentSender.SendIntentException ignored) {
// The installer no longer accepts callbacks.
}
}回调把内部 install return code 转换为 PackageInstaller 公共 status。发送失败不会回滚已经完成的安装,只表示调用方不再接收通知。具体错误码映射和 observer 细节留给 PMS056。
9. 状态机与边界
9.1 用户安装状态机
9.2 入口差异总结
| 维度 | adb/pm | 应用商店/安装器 | 系统启动扫描 |
|---|---|---|---|
| 入口 | PackageManagerShellCommand | PackageInstallerService Binder | InitAppsHelper |
| 中间对象 | Session + stage | Session + stage | scanFile |
| 典型 flags | INSTALL_FROM_ADB | 清除 ADB,通常 replace-existing | system scan flags |
| 用户确认 | shell 命令无 UI | 由调用方/系统策略决定 | 无用户确认 |
| 统一点 | InstallingSession → InstallPackageHelper | InstallingSession → InstallPackageHelper | initPackageTracedLI 内部扫描 |
| 失败清理 | abandon/remove code path | session cleanup/remove code path | 扫描失败不提交系统包 |
10. 测试与诊断
10.1 输入与断言
| 场景 | 断言 | 证明范围 |
|---|---|---|
| shell 无 APK 参数 | 命令级失败,不创建 session | Shell 入口校验 |
| APEX 带 split | 命令级拒绝 | 输入约束 |
| 普通 App 创建 session | AppOps/权限通过后 flags 被归一化 | Service 边界 |
| shell 创建 session | 设置 INSTALL_FROM_ADB | caller 识别 |
| commit 前写入失败 | session abandon,stage 清理 | 会话失败 |
| sealed 后 mutation | 被拒绝 | session 封存 |
| 多包 child 一个失败 | parent 不进入统一成功路径 | 原子调度 |
| prepare/scan/reconcile 失败 | request 有错误,commit 不执行 | 阶段短路 |
| dexopt 失败 | post-dexopt 走失败清理 | 异步阶段 |
| commit 成功 | Settings/resolver 状态更新,发送回调 | 生效时机 |
| 启动扫描失败 | 不创建用户 session | 入口边界 |
10.2 现场排查顺序
- 先判断请求来自 shell、PackageInstaller API 还是启动扫描。
- 对 session 路径检查 sessionId、sealed 状态、stage 目录和 child sessions。
- 查看
InstallRequest当前 return code,定位 Prepare、Scan、Reconcile、Dexopt 还是 Commit。 - 如果没有进入
InstallPackageHelper,优先检查 session 写入、seal 或复制阶段。 - 如果 commit 未发生,检查 reconcile 返回 null 或前置 request 是否已有错误。
- 如果系统状态已改变但调用方无结果,检查 post-install 和 IntentSender 回调,而不是重复执行安装。
11. 源码路线
建议按以下顺序阅读:
PackageManagerShellCommand.doRunInstall():看pm install如何创建、写入和提交 session。PackageInstallerService.createSessionInternal():看 caller、权限和 flags 归一化。PackageInstallerSession.commit():看 sealed 和 multi-package child 协调。createInstallingSession():看PackageLite和安装参数如何交给 InstallingSession。InstallingSession.start()/processPendingInstall():看复制结果如何进入 PMS handler。InstallPackageHelper.installPackagesTraced():看统一阶段编排和异步 dexopt 边界。initPackageTracedLI():对比启动扫描入口。commitPackagesLocked()、restoreAndPostInstall()和onInstallComplete():看状态生效、失败清理和通知。
12. 设计收束
Android 17 的安装全景可以压缩成两条主线。用户安装依次经过 Shell/App API、PackageInstallerService、PackageInstallerSession、InstallingSession 和 InstallRequest,然后进入 prepare、scan、reconcile、dexopt、commit。启动扫描则由 InitAppsHelper 调用 InstallPackageHelper.initPackageTracedLI(),直接完成系统包的 parse、scan 和 register。
两条主线共享 PMS 的包状态和注册结果,但不共享入口语义。Session 负责“可写输入何时封存”,InstallingSession 负责“stage 结果何时变成 InstallRequest”,InstallPackageHelper 负责“何时允许修改 PMS 状态”。理解这三个 owner,后续阅读安装前检查、APK 拷贝和安装后注册时,才能把具体函数放回正确的阶段,而不是把所有安装行为都归到一个巨大入口函数中。
