安装回调
本文面向已经读过 Session 安装 和 ADB 安装流程 的读者。主题不是罗列错误码,而是解释安装结果如何跨越三个边界:PMS 内部的 InstallRequest,旧式 IPackageInstallObserver2 Binder 回调,以及现代 PackageInstallerSession 的 IntentSender。读完后,读者应能定位结果码的 owner、理解 POST_INSTALL 为什么是异步屏障,并判断一个失败究竟发生在安装、恢复、清理还是结果分发阶段。
1. 回调边界
1.1 两种消费者
源码文件:frameworks/base/core/java/android/content/pm/IPackageInstallObserver2.aidl
oneway interface IPackageInstallObserver2 {
void onUserActionRequired(in Intent intent);
void onPackageInstalled(String basePackageName, int returnCode,
String msg, in Bundle extras);
}IPackageInstallObserver2 是隐藏的 one-way AIDL 接口,服务端调用不会等待客户端方法返回。它由旧式 PackageManager.installPackage* 路径使用;returnCode 是 PMS 内部 INSTALL_* 数值,msg 是诊断文本,extras 携带冲突包名、警告和开发者校验等附加信息。
现代 PackageInstaller 不把这个接口直接暴露给应用,而是由 session 内部创建一个 observer,再把结果转成 Intent 发给调用者提供的 IntentSender。因此“Binder observer 收到成功”与“应用收到 STATUS_SUCCESS”是同一结果的两个消费者,不是两个独立安装。
1.2 请求对象
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallRequest.java、InstallingSession.java
final IPackageInstallObserver2 mObserver;
private int mReturnCode;
private String mReturnMsg;
InstallRequest(InstallingSession params) {
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);
}
public IPackageInstallObserver2 getObserver() {
return mInstallArgs == null ? null : mInstallArgs.mObserver;
}InstallingSession 在 session 提交时持有 observer,InstallRequest 再把它封装进 InstallArgs。这条引用贯穿 prepare、scan、reconcile 和 commit;结果码也一直写在同一个 request 上。getObserver() 允许为空,因为 install-existing 或内部恢复请求可能只需要执行 runnable,不需要跨进程回调。
2. POST_INSTALL
2.1 创建 token
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
public void restoreAndPostInstall(InstallRequest request) {
int token;
if (mPm.mNextInstallToken < 0) {
mPm.mNextInstallToken = 1;
}
token = mPm.mNextInstallToken++;
synchronized (mPm.mRunningInstalls) {
mPm.mRunningInstalls.put(token, request);
}
final boolean succeeded =
request.getReturnCode() == PackageManager.INSTALL_SUCCEEDED;
if (succeeded) {
request.onRestoreStarted();
// 可能在这里交给 Backup Manager 或 Rollback Manager
}
mPm.mHandler.obtainMessage(PackageManagerService.POST_INSTALL,
token, 0).sendToTarget();
}token 是 PMS 内部关联键,不是 PackageInstaller 的 session id。请求先写入 mRunningInstalls,Handler 后续按 token 取回同一个对象;这使 restore、冻结器释放、清理和回调都能共享同一份状态。恢复动作异步完成时,Backup Manager 会稍后把同一个 token 送回 PMS,而不是提前通知安装成功。
2.2 Handler 消费
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageHandler.java
case POST_INSTALL: {
final InstallRequest request;
final int token = msg.arg1;
final boolean didRestore = msg.arg2 != 0;
synchronized (mPm.mRunningInstalls) {
request = mPm.mRunningInstalls.get(token);
mPm.mRunningInstalls.delete(token);
}
if (request == null) {
break;
}
request.onRestoreFinished();
request.closeFreezer();
request.onInstallCompleted();
request.runPostInstallRunnable();
if (!request.isInstallExistingForUser()) {
mPm.handlePackagePostInstall(request, didRestore);
}
}这里的顺序决定了回调看到的状态:先从运行表摘除,标记 restore 完成并关闭 package freezer,再执行 post-install runnable,最后交给 handlePackagePostInstall。因此回调不是 commit 返回的同步尾声,而是 Handler 消费 token 后才发生的异步阶段。token 丢失时直接结束,不会凭空构造一个成功结果。
3. PMS 分发
3.1 旧式 Observer
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
void notifyInstallObserver(InstallRequest request) {
if (request.getObserver() != null) {
try {
final Bundle extras = extrasForInstallResult(request);
request.getObserver().onPackageInstalled(
request.getName(), request.getReturnCode(),
request.getReturnMsg(), extras);
} catch (RemoteException e) {
Slog.i(TAG, "Observer no longer exists.");
}
}
}PMS 是结果码和 extras 的 owner,observer 只是消费者。extrasForInstallResult() 把 request 中的内部状态整理成公开附加数据;Binder 断开只记录日志,不重新执行安装,也不把已完成的请求改成失败。因为 AIDL 是 one-way,服务端不会等待客户端业务处理。
3.2 延迟回调
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
void scheduleDeferredNoKillInstallObserver(InstallRequest request) {
final String packageName = request.getPkg().getPackageName();
mNoKillInstallObservers.put(packageName, request);
final Message message = mHandler.obtainMessage(
DEFERRED_NO_KILL_INSTALL_OBSERVER, packageName);
mHandler.sendMessageDelayed(message, DEFERRED_NO_KILL_INSTALL_OBSERVER_DELAY_MS);
}
void scheduleDeferredPendingKillInstallObserver(InstallRequest request) {
final String packageName = request.getPkg().getPackageName();
mPendingKillInstallObservers.put(packageName, request);
mHandler.sendMessageDelayed(
mHandler.obtainMessage(DEFERRED_PENDING_KILL_INSTALL_OBSERVER, packageName),
DEFERRED_PENDING_KILL_INSTALL_OBSERVER_DELAY_MS);
}安装更新可能需要等待旧进程被杀死或延迟清理旧 code path。PMS 按包名把 request 放入两个不同 map,再由 Handler 选择 killApp 参数调用 notifyInstallObserver(packageName, killApp)。这解释了为什么 commit 已完成但 observer 仍可能稍后到达:回调时机受进程生命周期和清理策略约束,而不是单纯受文件写入约束。
4. Session 结果
4.1 内部 observer
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java
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));
}
}
};Session 内部把 legacy callback 转成 CompletableFuture<InstallResult>。成功结果携带 extras,失败结果把同一个 return code 和 message 封装成异常;onUserActionRequired 在这个路径是不允许的,因为 session commit 已经完成了用户交互阶段。future 完成只是 session 内部状态变化,真正对外通知还要经过 sendUpdateToRemoteStatusReceiver。
4.2 Handler 解耦
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java
private void sendUpdateToRemoteStatusReceiver(int returnCode, String msg,
Bundle extras, boolean forPreapproval) {
final IntentSender statusReceiver = forPreapproval
? getPreapprovalRemoteStatusReceiver()
: getRemoteStatusReceiver();
if (statusReceiver == null) {
return;
}
final SomeArgs args = SomeArgs.obtain();
args.arg1 = getPackageName();
args.arg2 = msg;
args.arg3 = extras;
args.arg4 = statusReceiver;
args.argi1 = returnCode;
args.argi2 = isPreapprovalRequested() && !isCommitted() ? 1 : 0;
mHandler.obtainMessage(MSG_ON_PACKAGE_INSTALLED, args).sendToTarget();
}源码注释明确说明:回调在另一个线程执行,避免 system_server 内部调用 session 时被调用方代码反向阻塞。SomeArgs 暂存包名、message、extras、receiver 和 return code,MSG_ON_PACKAGE_INSTALLED 是 session Handler 的异步边界。
4.3 Intent 字段
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java
private static void sendOnPackageInstalled(Context context, IntentSender target,
int sessionId, boolean showNotification, int userId,
String basePackageName, int returnCode, boolean isPreapproval,
String msg, Bundle extras) {
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, basePackageName);
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
PackageManager.installStatusToPublicStatus(returnCode));
fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
PackageManager.installStatusToString(returnCode, msg));
fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
fillIn.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL, isPreapproval);
if (extras != null) {
final String existing = extras.getString(
PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
if (!TextUtils.isEmpty(existing)) {
fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing);
}
final ArrayList<String> warnings =
extras.getStringArrayList(PackageInstaller.EXTRA_WARNINGS);
if (!ArrayUtils.isEmpty(warnings)) {
fillIn.putStringArrayListExtra(
PackageInstaller.EXTRA_WARNINGS, warnings);
}
}
target.sendIntent(context, 0, fillIn, null,
BroadcastOptions.makeBasic().toBundle(), null, null);
}现代调用方应优先读取 EXTRA_STATUS 和 EXTRA_STATUS_MESSAGE;需要和旧 API 对照时再读取 EXTRA_LEGACY_STATUS。EXTRA_OTHER_PACKAGE_NAME 和 warnings 由 extras 有条件地投影,不能假设每次失败都有冲突包名。EXTRA_PRE_APPROVAL 区分预批准通知和真正 commit 结果。
5. 错误与清理
5.1 结果码 owner
| 阶段 | 典型错误 | request 是否进入 POST_INSTALL | 调用方可见结果 |
|---|---|---|---|
| session 参数/用户检查 | 用户不存在、session 非法 | 通常会进入失败收尾 | 失败 status 与 message |
| parse/scan/reconcile | 无效 APK、签名不兼容、版本降级 | 是 | legacy code 与 status |
| commit 后清理 | native library、旧 code path 清理异常 | 是 | 可能成功但日志有清理错误 |
| restore | Backup/Rollback 未完成或恢复失败 | 由恢复回路决定 | 回调延后或失败 |
| receiver 断开 | RemoteException/SendIntentException | 安装状态不回滚 | 调用方收不到结果 |
表中最后一行很重要:结果发送失败不等于安装失败。PMS 不会因为客户端进程消失而恢复已提交的包;诊断时必须分别检查 request.getReturnCode() 和 receiver 发送异常。
5.2 清理与回调顺序
源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java
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());
}
}doPostInstall 先对所有 request 做清理,再调用 restoreAndPostInstall。失败安装的 code path 会被删除,成功移动安装则清理旧卷路径。回调建立在这个顺序之后,所以调用方收到失败时,staging code 通常已经进入删除流程;但延迟 no-kill 清理仍可能稍后执行。
6. 时序总图
7. 验证方法
7.1 成功与失败
执行一次正常 adb install,接收方记录 EXTRA_STATUS、EXTRA_LEGACY_STATUS、session id 和 package name。成功断言是 public status 为 success,legacy status 为 INSTALL_SUCCEEDED,并且 pm path 能找到已提交路径。再安装一个签名不匹配或版本降级 APK,断言 public status 为 failure、legacy status 保留具体 INSTALL_FAILED_*,且当前已安装版本不变。
7.2 receiver 消失
创建 session 后提交,再在结果到达前销毁接收 IntentSender 的进程。安装结果应仍能在 dumpsys package 中体现;日志可能出现 observer 或 SendIntentException,但 PMS 不会因此回滚包。这个实验区分“安装状态 owner”与“结果通知消费者”。
7.3 恢复延迟
对已安装应用执行一次会触发数据恢复或 rollback 检查的更新,记录 commit 时间与回调时间。若回调明显晚于 commit,应沿 restoreAndPostInstall、mRunningInstalls 和 POST_INSTALL token 查找,而不是把延迟归因于 Binder 网络传输。
8. 源码路线
IPackageInstallObserver2.aidl:明确旧式回调的参数和 one-way 语义。InstallingSession/InstallRequest:确认 observer、return code 和 request 的所有权。InstallPackageHelper.restoreAndPostInstall:理解 token、restore 和 POST_INSTALL 的关系。PackageHandler.doHandleMessage:确认 request 取回、冻结器释放和 runnable 顺序。PackageManagerService.notifyInstallObserver:观察 legacy Binder 回调和异常边界。PackageInstallerSession.sendUpdateToRemoteStatusReceiver:理解现代 IntentSender 的异步分发。sendOnPackageInstalled:核对 public status、legacy status、message 和 extras 的投影。
安装回调的关键不在“最后调用一个接口”,而在于 PMS 先把安装结果绑定到 InstallRequest,再用 token 穿过恢复与清理阶段,最后分别投影给 Binder Observer 和 IntentSender。掌握这条主线后,任何结果异常都可以按“状态是否提交、POST_INSTALL 是否消费、receiver 是否存活”三层定位。
