Skip to content

InstallingSession 会话

追踪 InstallingSession 的参数封装、单包/多包调度、复制结果和失败清理。

基于android-17.0.0_r1
AndroidPackageManagerServiceInstallingSessionPackageInstaller源码阅读

InstallingSession 会话 ​

本文承接 安装全景 和 InstallPackageHelper 入口。PMS043 画出了入口地图,PMS044 解释了安装阶段编排;本文把中间的 InstallingSession 展开,说明 PackageInstallerSession 如何把封存的 stage 转成内部任务、单包与多包如何汇合,以及复制失败时为何还没有进入 InstallPackageHelper 的主安装管道。

PackageInstallerSession 是面向 Binder 客户端的可写会话;InstallingSession 是 system_server 内部的执行上下文。前者负责写入、seal、持久化和客户端状态,后者负责把 origin、flags、用户、PackageLite、observer 等参数封装成 InstallRequest,再交给 PMS handler。

1. 对象边界 ​

1.1 会话到任务 ​

InstallingSession 的生命周期很短:创建后排队,执行一次 start/copy/process,最终把 request 交给 PMS 或完成失败清理。它不是会话持久化对象,也不是最终 PackageSetting。

1.2 两类构造场景 ​

构造函数场景mOriginInfomMoveInfomSessionId
(OriginInfo, MoveInfo, ...)move install原始路径非 null-1
(sessionId, stagedDir, SessionParams, ...)PackageInstaller sessionstaged 目录null有效 ID

两种构造函数共享后续调度方法,但默认值和 origin 语义不同。移动安装不是一个普通的 staged session,不能只看 mInstallFlags 判断安装来源。

2. 字段与所有权 ​

2.1 核心字段 ​

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

java
class InstallingSession {
    final OriginInfo mOriginInfo;
    final MoveInfo mMoveInfo;
    final IPackageInstallObserver2 mObserver;
    int mInstallFlags;
    final int mDevelopmentInstallFlags;
    @NonNull final InstallSource mInstallSource;
    final String mVolumeUuid;
    int mRet;
    final SigningDetails mSigningDetails;
    final int mInstallReason;
    final int mInstallScenario;
    @Nullable MultiPackageInstallingSession mParentInstallingSession;
    final boolean mForceQueryableOverride;
    final int mDataLoaderType;
    final long mRequiredInstalledVersionCode;
    final int mPackageSource;
    final PackageLite mPackageLite;
    String mTraceMethod;
    int mTraceCookie;
    private final UserHandle mUser;
    @NonNull final PackageManagerService mPm;
    final boolean mIsInherit;
    final int mSessionId;
    final int mRequireUserAction;
    final boolean mApplicationEnabledSettingPersistent;
    @Nullable final DomainSet mPreVerifiedDomains;
    final boolean mHasAppMetadataFile;
    @Nullable final String mDexoptCompilerFilter;
    final boolean mDependencyInstallerEnabled;
    final int mMissingSharedLibraryCount;
    @Nullable final DeveloperVerificationStatusInternal mDeveloperVerificationStatus;
    final List<String> mWarnings;
}

字段可分为四组:origin/目标卷描述输入;安装 flags、权限、签名和来源;session、用户、数据加载器和 split 约束;observer、trace、警告和验证状态。它们全部属于本次任务,不是全局安装状态。

2.2 可变字段 ​

mInstallFlags 和 mRet 是 InstallingSession 中最重要的可变字段。overrideInstallLocation() 可能只改变 INSTALL_INTERNAL 位;handleStartCopy() 和 copyApk() 通过 mRet 把位置决策、复制和 native library 结果传到 handleReturnCode()。其余大多数字段在构造后保持不变。

3. 构造函数 ​

3.1 Move install ​

java
InstallingSession(OriginInfo originInfo, MoveInfo moveInfo,
        IPackageInstallObserver2 observer, int installFlags,
        int developmentInstallFlags, InstallSource installSource,
        String volumeUuid, UserHandle user, String packageAbiOverride,
        int packageSource, PackageLite packageLite, PackageManagerService pm) {
    mPm = pm;
    mUser = user;
    mOriginInfo = originInfo;
    mMoveInfo = moveInfo;
    mObserver = observer;
    mInstallFlags = installFlags;
    mDevelopmentInstallFlags = developmentInstallFlags;
    mInstallSource = Preconditions.checkNotNull(installSource);
    mVolumeUuid = volumeUuid;
    mPackageAbiOverride = packageAbiOverride;
    mPermissionStates = new ArrayMap<>();
    mAllowlistedRestrictedPermissions = null;
    mAutoRevokePermissionsMode = MODE_DEFAULT;
    mSigningDetails = SigningDetails.UNKNOWN;
    mInstallReason = PackageManager.INSTALL_REASON_UNKNOWN;
    mInstallScenario = PackageManager.INSTALL_SCENARIO_DEFAULT;
    mForceQueryableOverride = false;
    mDataLoaderType = DataLoaderType.NONE;
    mRequiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
    mPackageSource = packageSource;
    mPackageLite = packageLite;
    mIsInherit = false;
    mSessionId = -1;
}

移动安装把签名、权限和数据加载器设为默认值,因为它复用已有包的代码和状态;真正的移动动作由 copyApkForMoveInstall() 调用 installer 完成。mSessionId=-1 明确表示它不是由 PackageInstaller session 创建。

3.2 标准 session ​

java
InstallingSession(int sessionId, File stagedDir,
        IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
        InstallSource installSource, UserHandle user, SigningDetails signingDetails,
        int installerUid, PackageLite packageLite, DomainSet preVerifiedDomains,
        PackageManagerService pm, boolean hasAppMetadatafile,
        boolean dependencyInstallerEnabled, int missingSharedLibraryCount,
        DeveloperVerificationStatusInternal developerVerificationStatus,
        List<String> warnings) {
    mPm = pm;
    mUser = user;
    mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
    mMoveInfo = null;
    mInstallReason = fixUpInstallReason(installSource.mInstallerPackageName,
            installerUid, sessionParams.installReason);
    mInstallScenario = sessionParams.installScenario;
    mObserver = observer;
    mInstallFlags = sessionParams.installFlags;
    mDevelopmentInstallFlags = sessionParams.developmentInstallFlags;
    mInstallSource = installSource;
    mVolumeUuid = sessionParams.volumeUuid;
    mPackageAbiOverride = sessionParams.abiOverride;
    mPermissionStates = sessionParams.getPermissionStates();
    mAllowlistedRestrictedPermissions = sessionParams.whitelistedRestrictedPermissions;
    mAutoRevokePermissionsMode = sessionParams.autoRevokePermissionsMode;
    mSigningDetails = signingDetails;
    mDataLoaderType = sessionParams.dataLoaderParams == null
            ? DataLoaderType.NONE : sessionParams.dataLoaderParams.getType();
    mPackageLite = packageLite;
    mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
    mSessionId = sessionId;
    mPreVerifiedDomains = preVerifiedDomains;
    mHasAppMetadataFile = hasAppMetadatafile;
    mDependencyInstallerEnabled = dependencyInstallerEnabled;
    mMissingSharedLibraryCount = missingSharedLibraryCount;
    mDeveloperVerificationStatus = developerVerificationStatus;
    mWarnings = new ArrayList<>(warnings);
}

标准 session 把客户端参数复制到内部对象,而不是持有可继续变化的 SessionParams。fixUpInstallReason() 会根据 installer UID 和 device/profile owner 修正安装原因;mIsInherit 则记录 split/inherit 模式。

4. 创建时机 ​

4.1 sealed 后创建 ​

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

java
@Nullable
private InstallingSession createInstallingSession(CompletableFuture<InstallResult> future)
        throws PackageManagerException {
    synchronized (mLock) {
        if (!mSealed) {
            throw new PackageManagerException(
                    INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed");
        }
        markStageDirInUseLocked();
    }

    final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
        @Override
        public void onUserActionRequired(Intent intent) {
            throw new IllegalStateException();
        }

        @Override
        public void onPackageInstalled(String basePackageName, int returnCode,
                String msg, Bundle extras) {
            if (returnCode == INSTALL_SUCCEEDED) {
                future.complete(new InstallResult(PackageInstallerSession.this, extras));
            } else {
                future.completeExceptionally(new PackageManagerException(returnCode, msg));
            }
        }
    };

只有 sealed session 才能创建 InstallingSession;stage 目录先标记为 in use,防止后续修改。local observer 把 PMS 的安装结果转换回 session 的 future,onUserActionRequired() 在这个内部路径被视为非法状态。

4.2 Lite 与用户 ​

java
    final UserHandle user;
    if ((params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
        user = UserHandle.ALL;
    } else {
        user = new UserHandle(userId);
    }

    if (!isMultiPackage() && !isApexSession()) {
        synchronized (mLock) {
            if (mPackageLite == null) {
                Slog.wtf(TAG, "Session: " + sessionId
                        + ". Don't have a valid PackageLite.");
            }
            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);
    }
}

多用户 flag 决定 UserHandle.ALL 或具体用户;普通 APK session 若尚无 PackageLite,在 stage 目录上补做轻量解析。APEX 和 multi-package 有各自的特殊处理,不应强行套用单 APK 路径。

5. 单包执行 ​

5.1 installStage ​

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

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);
}

private void start() {
    if (DEBUG_INSTALL) Slog.i(TAG, "start " + mUser + ": " + this);
    Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
            System.identityHashCode(this));
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startInstall");
    InstallRequest installRequest = new InstallRequest(this);
    handleStartCopy(installRequest);
    handleReturnCode(installRequest);
    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}

installStage() 不直接执行安装,而是把 start 投递到 PMS handler。start 创建 InstallRequest,再顺序调用“安装位置/准备”和“复制结果处理”。这保证安装任务在 PMS 指定线程上进入下一阶段。

5.2 handleStartCopy ​

java
private void handleStartCopy(InstallRequest request) {
    if ((mInstallFlags & PackageManager.INSTALL_APEX) != 0) {
        mRet = INSTALL_SUCCEEDED;
        return;
    }
    PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(
            mPm.mContext, mPackageLite, mOriginInfo.mResolvedPath,
            mInstallFlags, mPackageAbiOverride);

    boolean isStaged = (mInstallFlags & INSTALL_STAGED) != 0;
    if (isStaged) {
        Pair<Integer, String> ret = mPm.verifyReplacingVersionCode(
                pkgLite, mRequiredInstalledVersionCode, mInstallFlags);
        mRet = ret.first;
        if (mRet != INSTALL_SUCCEEDED) {
            request.setError(mRet, "Failed to verify version code: " + ret.second);
            return;
        }
    }

    if (!mOriginInfo.mStaged && pkgLite.recommendedInstallLocation
            == InstallLocationUtils.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
        pkgLite.recommendedInstallLocation = mPm.freeCacheForInstallation(
                pkgLite.recommendedInstallLocation, mPackageLite,
                mOriginInfo.mResolvedPath, mPackageAbiOverride, mInstallFlags);
    }
    mRet = overrideInstallLocation(pkgLite.packageName,
            pkgLite.recommendedInstallLocation, pkgLite.installLocation);
    if (mRet != INSTALL_SUCCEEDED) {
        request.setError(mRet, "Failed to override installation location");
    }
}

这个阶段只做最小包信息读取、staged 版本复核、必要时释放 cache,以及安装位置覆盖。APEX 直接跳过普通 APK 位置逻辑;具体签名、权限和版本全面检查由后续专题负责。

5.3 位置覆盖 ​

java
private int overrideInstallLocation(String packageName,
        int recommendedInstallLocation, int installLocation) {
    if (mOriginInfo.mStaged) {
        if (mOriginInfo.mFile != null) {
            mInstallFlags |= PackageManager.INSTALL_INTERNAL;
        } else {
            throw new IllegalStateException("Invalid stage location");
        }
    }
    if (recommendedInstallLocation < 0) {
        return InstallLocationUtils.getInstallationErrorCode(
                recommendedInstallLocation);
    }
    final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0;
    if (!onInt) {
        if (recommendedInstallLocation
                == InstallLocationUtils.RECOMMEND_INSTALL_EXTERNAL) {
            mInstallFlags &= ~PackageManager.INSTALL_INTERNAL;
        } else {
            mInstallFlags |= PackageManager.INSTALL_INTERNAL;
        }
    }
    return INSTALL_SUCCEEDED;
}

overrideInstallLocation() 只允许改变 INSTALL_INTERNAL,并把推荐位置转换成 flags。对于已 staged 的输入,位置已经确定,缺少 staged file 会抛出内部状态错误。

6. 复制与交接 ​

6.1 handleReturnCode ​

java
private void handleReturnCode(InstallRequest installRequest) {
    processPendingInstall(installRequest);
}

private void processPendingInstall(InstallRequest installRequest) {
    if (mRet == PackageManager.INSTALL_SUCCEEDED) {
        mRet = copyApk(installRequest);
    }
    if (mRet == PackageManager.INSTALL_SUCCEEDED) {
        F2fsUtils.releaseCompressedBlocks(
                mPm.mContext.getContentResolver(),
                new File(installRequest.getCodePath()));
    }
    installRequest.setReturnCode(mRet);
    if (mParentInstallingSession != null) {
        mParentInstallingSession.tryProcessInstallRequest(installRequest);
    } else {
        mPm.mHandler.post(() -> processInstallRequests(
                mRet == PackageManager.INSTALL_SUCCEEDED,
                Collections.singletonList(installRequest)));
    }
}

只有 mRet 仍为成功时才复制 APK;复制成功后释放压缩块,再把 return code 写入 request。单包直接投递 processInstallRequests(),多包则回传 parent,等待所有 child。

6.2 copyApk 分流 ​

java
private int copyApk(InstallRequest request) {
    if (mMoveInfo == null) {
        return copyApkForFileInstall(request);
    } else {
        return copyApkForMoveInstall(request);
    }
}

这条分流是 InstallingSession 的核心价值之一:普通文件安装和 move install 使用不同的 installer 操作,但向上都返回同一个整数状态并写入 InstallRequest。

6.3 普通文件安装 ​

java
private int copyApkForFileInstall(InstallRequest request) {
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyApk");
    try {
        if (mOriginInfo.mStaged) {
            request.setCodeFile(mOriginInfo.mFile);
            return PackageManager.INSTALL_SUCCEEDED;
        }
        final boolean isEphemeral =
                (mInstallFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
        request.setCodeFile(mPm.mInstallerService.allocateStageDirLegacy(
                mVolumeUuid, isEphemeral));

        int ret = PackageManagerServiceUtils.copyPackage(
                mOriginInfo.mFile.getAbsolutePath(), request.getCodeFile());
        if (ret != PackageManager.INSTALL_SUCCEEDED) {
            request.setError(ret, "Failed to copy package");
            return ret;
        }
        final File libraryRoot = new File(request.getCodeFile(), LIB_DIR_NAME);
        try (NativeLibraryHelper.Handle handle =
                     NativeLibraryHelper.Handle.create(request.getCodeFile())) {
            ret = NativeLibraryHelper.copyNativeBinariesWithOverride(
                    handle, libraryRoot, request.getAbiOverride(),
                    isIncrementalPath(request.getCodeFile().getAbsolutePath()));
        } catch (IOException e) {
            request.setError(PackageManagerException.ofInternalError(
                    "Copying native libraries failed", INTERNAL_ERROR_NATIVE_LIBRARY_COPY));
        }
        return ret;
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
}

已 staged 的 origin 直接复用文件,不再复制;普通文件安装先分配 code path,再复制 APK,最后处理 native libraries。Instant flag 只影响 stage 目录分配。完整的原子拷贝和目录命名留在 PMS047 展开。

7. 多包协调 ​

7.1 创建 parent ​

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

java
public void installStage(List<InstallingSession> children)
        throws PackageManagerException {
    final MultiPackageInstallingSession installingSession =
            new MultiPackageInstallingSession(getUser(), children, mPm);
    setTraceMethod("installStageMultiPackage")
            .setTraceCookie(System.identityHashCode(installingSession));
    Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER,
            "installStageMultiPackage", System.identityHashCode(installingSession));
    mPm.mHandler.post(installingSession::start);
}

parent 不代表一个待安装 APK,而是 child InstallingSession 的协调容器。构造时 child 数量为 0 会抛出内部错误;每个 child 都会回写自己的 mParentInstallingSession。

7.2 child 批量 start ​

java
MultiPackageInstallingSession(UserHandle user,
        List<InstallingSession> childInstallingSessions,
        PackageManagerService pm) throws PackageManagerException {
    if (childInstallingSessions.size() == 0) {
        throw PackageManagerException.ofInternalError("No child sessions found!",
                PackageManagerException.INTERNAL_ERROR_INSTALL_MISSING_CHILD_SESSIONS);
    }
    mPm = pm;
    mUser = user;
    mChildInstallingSessions = childInstallingSessions;
    for (InstallingSession child : childInstallingSessions) {
        child.mParentInstallingSession = this;
    }
    mCurrentInstallRequests = new ArraySet<>(mChildInstallingSessions.size());
}

public void start() {
    final ArrayList<InstallRequest> installRequests =
            new ArrayList<>(mChildInstallingSessions.size());
    for (InstallingSession child : mChildInstallingSessions) {
        InstallRequest installRequest = new InstallRequest(child);
        installRequests.add(installRequest);
        child.handleStartCopy(installRequest);
    }
    for (int i = 0; i < mChildInstallingSessions.size(); i++) {
        mChildInstallingSessions.get(i).handleReturnCode(installRequests.get(i));
    }
}

多包 start 先让所有 child 完成位置/准备,再让所有 child 处理复制结果。这样 parent 能看到整批 request,而不是某个 child 先进入 PMS 主安装流程。

7.3 等待全部结果 ​

java
public void tryProcessInstallRequest(InstallRequest request) {
    mCurrentInstallRequests.add(request);
    if (mCurrentInstallRequests.size() != mChildInstallingSessions.size()) {
        // Wait until all installRequests have finished copying.
        return;
    }
    int completeStatus = PackageManager.INSTALL_SUCCEEDED;
    for (InstallRequest installRequest : mCurrentInstallRequests) {
        if (installRequest.getReturnCode() == PackageManager.INSTALL_UNKNOWN) {
            return;
        } else if (installRequest.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
            completeStatus = installRequest.getReturnCode();
            break;
        }
    }
    final List<InstallRequest> requests = new ArrayList<>(mCurrentInstallRequests.size());
    for (InstallRequest installRequest : mCurrentInstallRequests) {
        installRequest.setReturnCode(completeStatus);
        requests.add(installRequest);
    }
    final int finalStatus = completeStatus;
    mPm.mHandler.post(() -> processInstallRequests(
            finalStatus == PackageManager.INSTALL_SUCCEEDED, requests));
}

parent 只有在收到全部 child request 后才继续;任一失败状态会成为整批状态,并写回所有 request。INSTALL_UNKNOWN 表示仍有 child 未完成,不能提前判定成功或失败。

8. 失败与清理 ​

8.1 APK 分流 ​

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);
    }
}

复制阶段失败不会进入 InstallPackageHelper.installPackagesTraced();InstallingSession 先清理失败 request,再恢复并通知。只有全部 child 复制成功,才把 request 列表交给 PMS044 所讲的主安装管道。

8.2 失败清理分支 ​

java
private void cleanUpForFailedInstall(InstallRequest request) {
    if (request.isInstallMove()) {
        mPm.cleanUpForMoveInstall(request.getMoveToUuid(),
                request.getMovePackageName(), request.getMoveFromCodePath());
    } else {
        mPm.removeCodePath(request.getCodeFile());
    }
}

move install 清理源/目标卷关系;普通安装删除本次 request 的 code path。清理依据是 request 已经产生的中间路径,而不是重新从 session 推导。

8.3 APEX 特殊分支 ​

java
if (!apexInstallRequests.isEmpty()) {
    if (success) {
        Thread t = new Thread(() -> installApexPackagesTraced(apexInstallRequests),
                "installApexPackages");
        t.start();
    } else {
        InstallRequest request = apexInstallRequests.get(0);
        mPm.notifyInstallObserver(request);
    }
    return;
}
processApkInstallRequests(success, installRequests);

InstallingSession 会先把 APEX request 与 APK request 分开;同一 multi-package 批次混合 APEX 和 APK 被视为非法状态。APEX 成功后走 apexd 交互线程,不直接进入 APK 的 processApkInstallRequests()。

9. 状态时序 ​

9.1 单包时序 ​

9.2 状态 owner ​

状态owner生效时机
writable/sealedPackageInstallerSessioncommit/seal
origin/flags/userInstallingSession构造时
copy return codeInstallingSession.mRet → InstallRequesthandleReturnCode
multi-package completeMultiPackageInstallingSession所有 child 完成复制
PMS 安装阶段InstallPackageHelperprocessInstallRequests 成功后
最终安装状态InstallRequest + observer/futurepost-install

10. 测试与诊断 ​

10.1 输入与断言 ​

场景断言覆盖范围
未 sealed sessioncreateInstallingSession 抛内部错误创建前置条件
普通 staged APKorigin 直接复用,不重复复制staged 分支
普通文件 APK生成 code path 并复制 native libsfile install
move install走 copyApkForMoveInstallorigin 分流
位置推荐失败request 有对应 return codeflags 决策
multi-package 空 child抛 missing child internal errorparent 构造
一个 child copy 失败所有 request 使用失败状态批量协调
child 未完成parent 保持等待INSTALL_UNKNOWN
APEX + APK 混合非法状态,不进入 APK 管道类型分流
复制失败code path/move path 清理且 observer 恢复失败收尾

测试应同时检查 session、InstallingSession、InstallRequest 和 stage/code path。只看最终 observer 状态,无法证明失败发生在复制前还是 InstallPackageHelper 阶段。

10.2 现场排查顺序 ​

  1. 确认 session 是否 sealed,以及 stageDir 是否已标记 in use。
  2. 查看 InstallingSession 的 mOriginInfo、mMoveInfo、mInstallFlags 和 mRet。
  3. 检查 PackageLite 是否存在,尤其是 split/multi-package 输入。
  4. 沿 handleStartCopy() 判断失败来自 staged 版本复核、空间、位置还是参数。
  5. 沿 copyApk() 判断是普通文件复制、native library 复制还是 move installer 失败。
  6. 对 multi-package 检查 parent 已收到的 child 数量和 INSTALL_UNKNOWN 状态。
  7. 最后确认是否进入 processInstallRequests(),以区分 InstallingSession 失败和 InstallPackageHelper 失败。

11. 源码路线 ​

建议按以下顺序阅读:

  1. PackageInstallerSession.createInstallingSession():sealed、stage 和 PackageLite。
  2. InstallingSession 字段与两个构造函数:参数 owner 和默认值。
  3. installStage() / start():handler 调度和 request 创建。
  4. handleStartCopy() / overrideInstallLocation():复制前决策。
  5. processPendingInstall() / copyApk():结果交接。
  6. MultiPackageInstallingSession:child 等待和整批状态。
  7. processApkInstallRequests() / cleanUpForFailedInstall():失败清理。
  8. PMS044 的 InstallPackageHelper.installPackagesTraced():确认交接后的下一阶段。

12. 设计收束 ​

InstallingSession 的核心是一次“内部执行上下文”转换:

text
sealed PackageInstallerSession
  -> InstallingSession(复制与调度参数)
  -> InstallRequest(一次包任务的可变状态)
  -> 单包直接交接 / 多包等待全部 child
  -> 成功进入 InstallPackageHelper,失败在本层清理

它把客户端会话的可写状态与 PMS 的安装阶段隔开:session 负责何时封存,InstallingSession 负责何时复制和汇报,InstallPackageHelper 负责何时扫描、协调和提交系统状态。后续阅读安装前检查和 APK 拷贝时,先定位这三层边界,才能准确判断某个错误是 session 状态错误、stage 输入错误,还是已经进入包安装主流程后的校验失败。