Skip to content

InstallPackageHelper 入口

追踪 InstallPackageHelper 的构造、安装阶段编排、扫描入口、锁与失败收尾。

基于android-17.0.0_r1
AndroidPackageManagerServiceInstallPackageHelperPackageInstaller源码阅读

InstallPackageHelper 入口 ​

本文承接 安装全景、解析错误处理 和 PMS 架构全景。PMS043 介绍了安装入口如何汇合;本文把汇合点展开到真实的 InstallPackageHelper,说明谁创建它、谁调用它、InstallRequest 如何穿过 prepare/scan/reconcile/dexopt/commit,以及失败时哪些状态必须清理。

本文不深入某一个校验规则、APK 文件拷贝、dexopt 参数或安装后广播。那些行为分别属于 PMS046、PMS047、PMS048 和 PMS049。这里的目标是建立入口类的源码地图,读者看完后应能把后续专题中的函数放回正确的调用阶段。

1. 类的职责 ​

1.1 编排器职责 ​

InstallPackageHelper 自己不代表某一个 APK,也不拥有最终的包对象;它协调多个 helper,并通过 InstallRequest 传递一次安装的中间状态。真正修改 PMS 全局状态的动作集中在 commit 阶段。

1.2 两个入口族 ​

入口族典型方法输入是否有 session
用户/会话安装installPackagesTracedList<InstallRequest>有,通常来自 InstallingSession
系统启动/重新扫描initPackageTracedLIscan file、parse flags、scan flags无 PackageInstaller session

两者都使用 InstallPackageHelper,但生命周期不同:用户安装先产生 staged code path 和 InstallRequest;系统扫描直接解析并扫描文件。

2. 构造与依赖 ​

2.1 字段 ​

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

java
final class InstallPackageHelper {
    private final PackageManagerService mPm;
    private final AppDataHelper mAppDataHelper;
    private final BroadcastHelper mBroadcastHelper;
    private final RemovePackageHelper mRemovePackageHelper;
    private final DeletePackageHelper mDeletePackageHelper;
    private final IncrementalManager mIncrementalManager;
    private final ApexManager mApexManager;
    private final Context mContext;
    private final PackageAbiHelper mPackageAbiHelper;
    private final SharedLibrariesImpl mSharedLibraries;
    private final PackageManagerServiceInjector mInjector;
    private final UpdateOwnershipHelper mUpdateOwnershipHelper;

    private final Object mInternalLock = new Object();
    @GuardedBy("mInternalLock")
    private PowerManager.WakeLock mInstallingWakeLock;

    // List of packages being installed.
    private final Set<String> mInstallingPackages;
}

依赖覆盖应用数据、广播、删除、增量文件系统、APEX、ABI、共享库和 update ownership。它们都属于安装流程的协作者;mPm 则提供 Settings、锁、handler、用户和全局包表。

2.2 构造函数 ​

java
// TODO(b/198166813): remove PMS dependency
InstallPackageHelper(PackageManagerService pm,
        AppDataHelper appDataHelper,
        RemovePackageHelper removePackageHelper,
        DeletePackageHelper deletePackageHelper,
        BroadcastHelper broadcastHelper) {
    mPm = pm;
    mInjector = pm.mInjector;
    mAppDataHelper = appDataHelper;
    mBroadcastHelper = broadcastHelper;
    mRemovePackageHelper = removePackageHelper;
    mDeletePackageHelper = deletePackageHelper;
    mIncrementalManager = pm.mInjector.getIncrementalManager();
    mApexManager = pm.mInjector.getApexManager();
    mContext = pm.mInjector.getContext();
    mPackageAbiHelper = pm.mInjector.getAbiHelper();
    mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
    mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
    mInstallingPackages = new ArraySet<>();
}

构造函数由 PMS 创建唯一的 helper 实例。部分依赖直接传入,部分依赖通过 injector 获取,这是当前 PMS 拆分过程中的真实形态;不能据此推断 helper 是完全独立于 PMS 的服务。

3. InstallRequest ​

3.1 Request 状态 ​

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

java
final class InstallRequest {
    private final int mUserId;
    @Nullable private final InstallArgs mInstallArgs;
    @Nullable private Runnable mPostInstallRunnable;
    @Nullable private PackageRemovedInfo mRemovedInfo;
    @Nullable private ParsedPackage mParsedPackage;
    @Nullable private ScanResult mScanResult;
    private int mReturnCode;
    private int mInternalErrorCode;
    @Nullable private PackageLite mPackageLite;
    @NonNull private final ArrayList<String> mWarnings;
}

InstallRequest 同时记录输入参数、解析结果、扫描结果、PackageSetting 和返回码。阶段函数不通过返回一个巨大 tuple 传递状态,而是逐步写入 request,后续失败清理也依赖这些字段。

3.2 构造 Request ​

java
// New install
InstallRequest(InstallingSession params) {
    mUserId = params.getUser().getIdentifier();
    mInstallArgs = new InstallArgs(params.mOriginInfo, params.mMoveInfo, params.mObserver,
            params.mInstallFlags, params.mDevelopmentInstallFlags, params.mInstallSource,
            params.mVolumeUuid, params.getUser(), null /*instructionSets*/,
            params.mPackageAbiOverride, params.mPermissionStates,
            params.mAllowlistedRestrictedPermissions, params.mAutoRevokePermissionsMode,
            params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
            params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
            params.mDataLoaderType, params.mPackageSource,
            params.mApplicationEnabledSettingPersistent, params.mDexoptCompilerFilter);
    mPackageLite = params.mPackageLite;
    mWarnings = new ArrayList<>(params.mWarnings);
}

InstallingSession 到 InstallRequest 是一次具体安装任务的封装边界。PackageLite 仍是轻量信息,完整 ParsedPackage 在 prepare 阶段解析后才写入 request。

4. 安装主入口 ​

4.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,scan 成功才 reconcile,reconcile 成功才 rename 和 dexopt。pendingForDexopt 很关键:进入异步 dexopt 后,finally 不能立即释放流程资源或发送最终完成处理。

4.2 阶段图 ​

5. Prepare 与 Scan ​

5.1 Prepare 阶段 ​

java
private boolean prepareInstallPackages(List<InstallRequest> requests) {
    for (InstallRequest request : requests) {
        try {
            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
            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();
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
    }
    return true;
}

Prepare 的具体检查由 preparePackage() 完成;本入口只负责按 request 循环、记录失败上下文和保证 trace/callback 成对结束。一个 request 失败会让整批 prepare 返回 false。

5.2 Scan 阶段 ​

java
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;
        }
        request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
        final String packageName = packageToScan.getPackageName();
        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(packageName)) {
                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;
}

Scan 依赖 prepare 产出的 ParsedPackage,调用 scanPackageTraced() 获得 ScanResult,并在同一批请求内拒绝重复 package name。createdAppId 和 versionInfos 会被后续 reconcile/失败清理使用。

6. Reconcile/Commit ​

6.1 Reconcile 锁 ​

java
private List<ReconciledPackage> reconcileInstallPackages(
        List<InstallRequest> requests,
        Map<String, Settings.VersionInfo> versionInfos) {
    try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
        synchronized (mPm.mLock) {
            try {
                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
                return ReconcilePackageUtils.reconcilePackages(
                        requests, Collections.unmodifiableMap(mPm.mPackages),
                        versionInfos, mSharedLibraries,
                        mPm.mSettings.getKeySetManagerService(),
                        mPm.mSettings, mPm.mInjector.getSystemConfig());
            } catch (ReconcileFailure e) {
                for (InstallRequest request : requests) {
                    request.setError("Reconciliation failed...", e);
                }
                return null;
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
        }
    }
}

Reconcile 同时持有 mInstallLock 和 mPm.mLock,把新扫描结果与现有包、共享库、签名和 Settings 做一致性比较。它返回 null 表示整批不能提交,而不是返回一个空的成功列表。

6.2 Commit 写状态 ​

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

java
@GuardedBy("mPm.mLock")
private void commitPackagesLocked(List<ReconciledPackage> reconciledPackages,
        @NonNull int[] allUsers) {
    // Only unavoidable failures should remain at this point.
    for (ReconciledPackage reconciledPkg : reconciledPackages) {
        final InstallRequest request = reconciledPkg.mInstallRequest;
        final ParsedPackage parsedPackage = request.getParsedPackage();
        final String packageName = parsedPackage.getPackageName();
        // Update PackageSetting, package tables and related state here.
        commitReconciledScanResultLocked(reconciledPkg, allUsers);
    }
}

源码注释把 commit 定义为允许修改系统状态的阶段。前面的阶段应尽量把可预测错误全部暴露出来;commit 内仍可能有 I/O 等不可避免错误,因此失败收尾必须能处理部分写入状态。

6.3 同步/异步 Commit ​

java
private boolean commitInstallPackages(List<ReconciledPackage> reconciledPackages) {
    try (PackageManagerTracedLock installLock = mPm.mInstallLock.acquireLock()) {
        for (ReconciledPackage reconciledPkg : reconciledPackages) {
            InstallRequest request = reconciledPkg.mInstallRequest;
            PackageFreezer freezer = freezePackageForInstall(
                    request.getParsedPackage().getPackageName(), UserHandle.USER_ALL,
                    request.getInstallFlags(), "installPackageLI",
                    ApplicationExitInfo.REASON_PACKAGE_UPDATED, request);
            request.setFreezer(freezer);
        }
        synchronized (mPm.mLock) {
            commitPackagesLocked(reconciledPackages, mPm.mUserManager.getUserIds());
        }
        executePostCommitStepsLIF(reconciledPackages);
    }
    return true;
}

默认路径在 mInstallLock 内冻结包,再在 mPm.mLock 下 commit。启用 app restart after update 的路径会把冻结等待和 commit 放入 commitInstallPackagesAsync(),但最终仍回到同一 commitPackagesLocked()。

7. WakeLock 与并发 ​

7.1 获取 WakeLock ​

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

java
private long acquireWakeLock(int count) {
    if (!mPm.isSystemReady()) {
        return -1;
    }
    synchronized (mInternalLock) {
        if (mInstallingWakeLock == null) {
            PowerManager pwm = mContext.getSystemService(PowerManager.class);
            if (pwm != null) {
                mInstallingWakeLock = pwm.newWakeLock(
                        PowerManager.PARTIAL_WAKE_LOCK, INSTALLER_WAKE_LOCK_TAG);
            } else {
                Slog.w(TAG, "Unable to obtain power manager while obtaining wake lock");
                return -1;
            }
        }
        mInstallingWakeLock.acquire(WAKELOCK_TIMEOUT_MS * count);
        return SystemClock.elapsedRealtime();
    }
}

系统未 ready 时不获取 WakeLock;正常情况下按 request 数量扩大超时。mInternalLock 只保护 WakeLock 生命周期,不等同于 PMS 的 mLock。

7.2 正在安装集合 ​

java
private void completeInstallProcess(List<InstallRequest> requests,
        Map<String, Boolean> createdAppId, boolean success) {
    synchronized (mInstallingPackages) {
        for (InstallRequest request : requests) {
            ParsedPackage parsedPkg = request.getParsedPackage();
            if (parsedPkg == null) {
                // Installation failed before parsing.
                continue;
            }
            mInstallingPackages.remove(parsedPkg.getPackageName());
        }
    }
    if (!success) {
        for (InstallRequest request : requests) {
            if (request.getParsedPackage() != null
                    && createdAppId.getOrDefault(
                            request.getParsedPackage().getPackageName(), false)) {
                cleanUpAppIdCreations(request);
            }
        }
    }
}

完成处理会释放“正在安装”占位,并在失败时回收本次乐观创建的 appId。解析前失败的 request 没有 ParsedPackage,因此不能用 package name 清理,必须依赖 request 中已有的 code/origin 信息。

7.3 释放 WakeLock ​

java
private void releaseWakeLock(final long acquireTime, int count) {
    if (acquireTime < 0) {
        return;
    }
    synchronized (mInternalLock) {
        if (mInstallingWakeLock == null) {
            return;
        }
        if (mInstallingWakeLock.isHeld()) {
            mInstallingWakeLock.release();
        }
    }
}

释放必须与顶层流程和异步 dexopt回调配对。pendingForDexopt=true 时顶层 finally 不释放,doPostDexopt() 完成 commit 或失败收尾后才释放。

8. 系统扫描入口 ​

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

这是系统分区、更新系统包和重新扫描路径的入口。它直接返回 AndroidPackage,没有用户安装流程中的 InstallRequest 列表,也不经过 prepareInstallPackages()。

8.2 PMS 委托 ​

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

java
AndroidPackage initPackageTracedLI(File scanFile,
        final int parseFlags, int scanFlags) throws PackageManagerException {
    return mInstallPackageHelper.initPackageTracedLI(scanFile, parseFlags, scanFlags);
}

void installPackagesTraced(List<InstallRequest> requests, MoveInfo moveInfo) {
    mInstallPackageHelper.installPackagesTraced(requests, moveInfo);
}

PMS 对外或内部其他模块只看到委托方法,helper 承担实际编排。这样可以把 PMS 主类的 Binder/API 入口与安装内部状态机隔离开。

9. 失败收尾 ​

9.1 Post-install ​

java
private void doPostInstall(List<InstallRequest> requests, MoveInfo moveInfo) {
    for (InstallRequest request : requests) {
        doPostInstallCleanUp(request, moveInfo);
    }
    for (InstallRequest request : requests) {
        restoreAndPostInstall(request);
    }
}

private void doPostInstallCleanUp(InstallRequest request, MoveInfo moveInfo) {
    if (moveInfo != null) {
        if (request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
            mRemovePackageHelper.cleanUpForMoveInstall(
                    moveInfo.mFromUuid, moveInfo.mPackageName, moveInfo.mFromCodePath);
        } else {
            mRemovePackageHelper.cleanUpForMoveInstall(
                    moveInfo.mToUuid, moveInfo.mPackageName, moveInfo.mFromCodePath);
        }
    } else if (request.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
        mRemovePackageHelper.removeCodePath(request.getCodeFile());
    }
}

post-install 先清理 move/code path,再恢复 request 并发送最终安装结果。它不是“安装成功后的附加逻辑”,失败路径同样必须经过这里。

9.2 成功标记的时机 ​

installPackagesTraced() 中局部变量 success 初始为 false;同步路径只有 commitInstallPackages() 成功返回后才在 doPostDexopt() 中设为 true。异步路径则由 callback 根据 dexopt 和 commit 结果调用 completeInstallProcess()。因此不要把“进入 commit”或“写入 stage”当成成功标志。

10. 测试与诊断 ​

10.1 输入与断言 ​

场景断言覆盖范围
空 request 列表不创建无意义状态,WakeLock 处理稳定顶层入口
request 无 ParsedPackageScan 返回 session invalidPrepare/Scan 边界
multi-package 重复包名Scan 失败且不进入 reconcile批量约束
ReconcileFailure所有 request 写入错误,返回 null阶段短路
dexopt 异步等待顶层 finally 不提前释放 WakeLock异步边界
commit 成功commitPackagesLocked 修改 Settings/resolver生效时机
commit 失败appId/code path/freezer 被清理失败收尾
系统扫描initPackageTracedLI 不创建 session双入口边界
多次同包安装mInstallingPackages 阻止重复并发状态并发控制

测试需要观察 request、helper 全局集合和 Settings 三层状态;仅断言最终安装码无法证明失败是否发生在 prepare、scan、reconcile 还是 commit。

10.2 现场排查顺序 ​

  1. 根据 trace 名称确认进入的是 installPackages 还是 scanPackage。
  2. 检查 request 是否有 ParsedPackage、ScanResult 和当前 return code。
  3. 沿 prepare/scan/reconcile 的短路条件定位第一个 false/null。
  4. 查看 mInstallingPackages 是否残留包名,判断是否完成收尾。
  5. 如果流程进入 dexopt,检查 pendingForDexopt 对应的 callback 是否执行。
  6. 最后检查 commit 锁、PackageSetting、code path 和 observer 通知。

11. 源码路线 ​

建议按以下顺序阅读:

  1. PackageManagerService 的委托方法:确认 helper 的调用边界。
  2. InstallPackageHelper 构造函数和字段:建立依赖地图。
  3. InstallRequest 构造函数:理解一次安装状态如何封装。
  4. installPackagesTraced():掌握阶段短路和异步 dexopt 边界。
  5. prepareInstallPackages()、scanInstallPackages():查看 request 状态写入。
  6. reconcileInstallPackages():理解双锁和 ReconciledPackage。
  7. commitInstallPackages()/commitPackagesLocked():确认系统状态生效时机。
  8. completeInstallProcess()、doPostInstall():跟踪成功/失败收尾。
  9. initPackageTracedLI():对比系统扫描入口。

12. 设计收束 ​

InstallPackageHelper 的核心不是某个单独函数,而是三条约束:

text
InstallRequest 是状态载体
  -> 阶段按 prepare/scan/reconcile/dexopt/commit 顺序短路
  -> commit 才允许修改 PMS 全局状态
  -> finally/callback 负责统一清理、解锁和通知

用户安装从 InstallingSession 带着 stage 和 PackageLite 进入,系统扫描则从 initPackageTracedLI() 直接进入 parse/scan;两者最终都由 PMS 的包表、Settings 和 resolver 接收结果。后续阅读具体安装专题时,应始终先定位函数属于哪条入口、读写哪个 request 字段、持有哪些锁,以及失败后由哪个收尾函数接管。