Skip to content

安装回调

追踪 Android 17 安装结果从 InstallRequest、POST_INSTALL 到 Binder Observer 和 IntentSender 的分发路径。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageInstaller安装回调源码阅读

安装回调 ​

本文面向已经读过 Session 安装 和 ADB 安装流程 的读者。主题不是罗列错误码,而是解释安装结果如何跨越三个边界:PMS 内部的 InstallRequest,旧式 IPackageInstallObserver2 Binder 回调,以及现代 PackageInstallerSession 的 IntentSender。读完后,读者应能定位结果码的 owner、理解 POST_INSTALL 为什么是异步屏障,并判断一个失败究竟发生在安装、恢复、清理还是结果分发阶段。

1. 回调边界 ​

1.1 两种消费者 ​

源码文件:frameworks/base/core/java/android/content/pm/IPackageInstallObserver2.aidl

java
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

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

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

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

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

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

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

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

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 清理异常是可能成功但日志有清理错误
restoreBackup/Rollback 未完成或恢复失败由恢复回路决定回调延后或失败
receiver 断开RemoteException/SendIntentException安装状态不回滚调用方收不到结果

表中最后一行很重要:结果发送失败不等于安装失败。PMS 不会因为客户端进程消失而恢复已提交的包;诊断时必须分别检查 request.getReturnCode() 和 receiver 发送异常。

5.2 清理与回调顺序 ​

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

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. 源码路线 ​

  1. IPackageInstallObserver2.aidl:明确旧式回调的参数和 one-way 语义。
  2. InstallingSession / InstallRequest:确认 observer、return code 和 request 的所有权。
  3. InstallPackageHelper.restoreAndPostInstall:理解 token、restore 和 POST_INSTALL 的关系。
  4. PackageHandler.doHandleMessage:确认 request 取回、冻结器释放和 runnable 顺序。
  5. PackageManagerService.notifyInstallObserver:观察 legacy Binder 回调和异常边界。
  6. PackageInstallerSession.sendUpdateToRemoteStatusReceiver:理解现代 IntentSender 的异步分发。
  7. sendOnPackageInstalled:核对 public status、legacy status、message 和 extras 的投影。

安装回调的关键不在“最后调用一个接口”,而在于 PMS 先把安装结果绑定到 InstallRequest,再用 token 穿过恢复与清理阶段,最后分别投影给 Binder Observer 和 IntentSender。掌握这条主线后,任何结果异常都可以按“状态是否提交、POST_INSTALL 是否消费、receiver 是否存活”三层定位。