Skip to content

安装全景

建立 adb、PackageInstaller 和系统扫描三条安装入口的统一阶段模型。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageInstallerInstallPackageHelper源码阅读

安装全景 ​

本文承接 解析错误处理、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主要状态
PackageManagerShellCommandsystem_server shell 命令命令参数、目标用户、文件列表
PackageInstallerServicePMSsession 集合、权限与 flags 归一化
PackageInstallerSession安装会话stage 目录、写入/封存、回调、子会话
InstallingSessionPMS 安装线程origin、flags、PackageLite、observer
InstallRequest一次具体包请求prepare/scan/reconcile/commit 结果
InstallPackageHelperPMS安装阶段编排和最终提交
PackageSettingPMS Settings已安装包状态、per-user 状态和瞬态库信息

2. Shell 入口 ​

2.1 pm install 入口 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java

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 创建、写入与提交 ​

java
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 调用 ​

java
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

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 ​

java
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 ​

java
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

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 消息链 ​

java
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 创建安装任务 ​

java
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

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 单包与多包调度 ​

java
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 ​

java
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

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 ​

阶段函数主要状态变化
PrepareprepareInstallPackagesorigin、包信息、安装位置和前置检查结果
ScanscanInstallPackagesParsedPackage 进入 ScanResult,注册 appId 候选
ReconcilereconcileInstallPackages与现有包、Settings、共享库和签名状态比较
CommitcommitPackagesLocked在 PMS lock 下修改系统包状态

源码注释特别强调:Commit 是安装流程中允许修改系统状态的阶段,且可预测错误应在此前确定。这个约束解释了为什么 scan/reconcile 要先完成,不能把失败留到提交中再猜测恢复方式。

6.3 前置阶段失败 ​

java
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

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

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

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 最终通知 ​

java
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应用商店/安装器系统启动扫描
入口PackageManagerShellCommandPackageInstallerService BinderInitAppsHelper
中间对象Session + stageSession + stagescanFile
典型 flagsINSTALL_FROM_ADB清除 ADB,通常 replace-existingsystem scan flags
用户确认shell 命令无 UI由调用方/系统策略决定无用户确认
统一点InstallingSession → InstallPackageHelperInstallingSession → InstallPackageHelperinitPackageTracedLI 内部扫描
失败清理abandon/remove code pathsession cleanup/remove code path扫描失败不提交系统包

10. 测试与诊断 ​

10.1 输入与断言 ​

场景断言证明范围
shell 无 APK 参数命令级失败,不创建 sessionShell 入口校验
APEX 带 split命令级拒绝输入约束
普通 App 创建 sessionAppOps/权限通过后 flags 被归一化Service 边界
shell 创建 session设置 INSTALL_FROM_ADBcaller 识别
commit 前写入失败session abandon,stage 清理会话失败
sealed 后 mutation被拒绝session 封存
多包 child 一个失败parent 不进入统一成功路径原子调度
prepare/scan/reconcile 失败request 有错误,commit 不执行阶段短路
dexopt 失败post-dexopt 走失败清理异步阶段
commit 成功Settings/resolver 状态更新,发送回调生效时机
启动扫描失败不创建用户 session入口边界

10.2 现场排查顺序 ​

  1. 先判断请求来自 shell、PackageInstaller API 还是启动扫描。
  2. 对 session 路径检查 sessionId、sealed 状态、stage 目录和 child sessions。
  3. 查看 InstallRequest 当前 return code,定位 Prepare、Scan、Reconcile、Dexopt 还是 Commit。
  4. 如果没有进入 InstallPackageHelper,优先检查 session 写入、seal 或复制阶段。
  5. 如果 commit 未发生,检查 reconcile 返回 null 或前置 request 是否已有错误。
  6. 如果系统状态已改变但调用方无结果,检查 post-install 和 IntentSender 回调,而不是重复执行安装。

11. 源码路线 ​

建议按以下顺序阅读:

  1. PackageManagerShellCommand.doRunInstall():看 pm install 如何创建、写入和提交 session。
  2. PackageInstallerService.createSessionInternal():看 caller、权限和 flags 归一化。
  3. PackageInstallerSession.commit():看 sealed 和 multi-package child 协调。
  4. createInstallingSession():看 PackageLite 和安装参数如何交给 InstallingSession。
  5. InstallingSession.start() / processPendingInstall():看复制结果如何进入 PMS handler。
  6. InstallPackageHelper.installPackagesTraced():看统一阶段编排和异步 dexopt 边界。
  7. initPackageTracedLI():对比启动扫描入口。
  8. 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 拷贝和安装后注册时,才能把具体函数放回正确的阶段,而不是把所有安装行为都归到一个巨大入口函数中。