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 |
|---|---|---|---|
| 用户/会话安装 | installPackagesTraced | List<InstallRequest> | 有,通常来自 InstallingSession |
| 系统启动/重新扫描 | initPackageTracedLI | scan 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
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 构造函数
// 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
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
// 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
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 阶段
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 阶段
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 锁
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
@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
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
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 正在安装集合
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
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
@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
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
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 无 ParsedPackage | Scan 返回 session invalid | Prepare/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 现场排查顺序
- 根据 trace 名称确认进入的是
installPackages还是scanPackage。 - 检查 request 是否有
ParsedPackage、ScanResult和当前 return code。 - 沿 prepare/scan/reconcile 的短路条件定位第一个 false/null。
- 查看
mInstallingPackages是否残留包名,判断是否完成收尾。 - 如果流程进入 dexopt,检查
pendingForDexopt对应的 callback 是否执行。 - 最后检查 commit 锁、PackageSetting、code path 和 observer 通知。
11. 源码路线
建议按以下顺序阅读:
PackageManagerService的委托方法:确认 helper 的调用边界。InstallPackageHelper构造函数和字段:建立依赖地图。InstallRequest构造函数:理解一次安装状态如何封装。installPackagesTraced():掌握阶段短路和异步 dexopt 边界。prepareInstallPackages()、scanInstallPackages():查看 request 状态写入。reconcileInstallPackages():理解双锁和 ReconciledPackage。commitInstallPackages()/commitPackagesLocked():确认系统状态生效时机。completeInstallProcess()、doPostInstall():跟踪成功/失败收尾。initPackageTracedLI():对比系统扫描入口。
12. 设计收束
InstallPackageHelper 的核心不是某个单独函数,而是三条约束:
InstallRequest 是状态载体
-> 阶段按 prepare/scan/reconcile/dexopt/commit 顺序短路
-> commit 才允许修改 PMS 全局状态
-> finally/callback 负责统一清理、解锁和通知用户安装从 InstallingSession 带着 stage 和 PackageLite 进入,系统扫描则从 initPackageTracedLI() 直接进入 parse/scan;两者最终都由 PMS 的包表、Settings 和 resolver 接收结果。后续阅读具体安装专题时,应始终先定位函数属于哪条入口、读写哪个 request 字段、持有哪些锁,以及失败后由哪个收尾函数接管。
