Skip to content

Session 安装

追踪 PackageInstaller 会话的创建、写入、封存、校验、提交和失败清理。

基于android-17.0.0_r1
AndroidPackageManagerServicePackageInstallerPackageInstallerSession源码阅读

Session 安装 ​

本文承接 安装全景、InstallingSession 会话 和 安装后注册。PMS045 解释了内部 InstallingSession 如何执行任务;本文退回客户端会话边界,追踪 PackageInstallerService 和 PackageInstallerSession 如何把写入的 APK 封存、校验,再交给内部安装任务。

本文聚焦非 staged 的普通 session 安装。staged session 会在 StagingManager 和 apexd 路径中继续分叉,留给 PMS051;静默安装、ADB 命令细节和回调状态码也分别由后续专题展开。

1. 会话边界 ​

1.1 API 到 PMS ​

PackageInstallerSession 是客户端可调用的 Binder 对象;InstallingSession 是 commit 之后的内部执行对象。两者名称相近但 owner 不同,不能把 session 的 mSealed、mCommitted 与 InstallingSession 的 mRet 混为一个状态机。

1.2 状态 owner ​

状态owner说明
session registeredPackageInstallerService.mSessions可被 sessionId 找到
writablePackageInstallerSessionstage 可写
sealedPackageInstallerSession.mSealed禁止继续写入
stage in usemStageDirInUseabandon 不再删除 stage
internal installInstallingSession/InstallRequest进入 PMS 安装阶段
applied/failedPackageInstallerSession future/callback对客户端报告结果

2. Service 管理 ​

2.1 创建入口 ​

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

java
@Override
public int createSession(SessionParams params, String installerPackageName,
        String callingAttributionTag, int userId) {
    try {
        if (params.dataLoaderParams != null
                && mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2)
                != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("You need the USE_INSTALLER_V2 permission");
        }
        // Draft sessions cannot be created through the public API.
        params.installFlags &= ~PackageManager.INSTALL_UNARCHIVE_DRAFT;
        return createSessionInternal(params, installerPackageName,
                callingAttributionTag, Binder.getCallingUid(), userId);
    } catch (IOException e) {
        throw ExceptionUtils.wrap(e);
    }
}

data loader 权限、公开 API draft 限制和 I/O 异常都在 service 边界处理。调用方拿到 sessionId 前,session 尚未进入写入阶段。

2.2 身份与 flags ​

java
int createSessionInternal(SessionParams params, String installerPackageName,
        String installerAttributionTag, int callingUid, int userId) throws IOException {
    final Computer snapshot = mPm.snapshotComputer();
    snapshot.enforceCrossUserPermission(callingUid, userId, true, true, "createSession");

    if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
        throw new SecurityException("User restriction prevents installing");
    }

    if (PackageManagerServiceUtils.isRootOrShell(callingUid)
            || PackageInstallerSession.isSystemDataLoaderInstallation(params)
            || PackageManagerServiceUtils.isAdoptedShell(callingUid, mContext)) {
        params.installFlags |= PackageManager.INSTALL_FROM_ADB;
        installerPackageName = SHELL_PACKAGE_NAME;
    } else {
        if (callingUid != SYSTEM_UID) {
            mAppOps.checkPackage(callingUid, installerPackageName);
        }
        params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
        params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
        params.installFlags &= ~PackageManager.INSTALL_ARCHIVED;
        params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
        params.developmentInstallFlags = 0;
    }

这里把 caller 身份投影到安装参数:shell/root 进入 ADB 分支,普通应用清除特权 flags 并默认 replace-existing。后续 session 和 InstallingSession 读取的是归一化后的 SessionParams。

2.3 分配与注册 ​

java
final int sessionId = allocateSessionIdLocked();
final File stageDir = buildSessionDir(sessionId, params);
final PackageInstallerSession session = new PackageInstallerSession(
        mInternalCallback, mContext, mPm, mInstallThread.getLooper(),
        mStagingManager, sessionId, userId, callingUid, installSource,
        params, stageDir, /*isApex=*/ false);

synchronized (mSessions) {
    mSessions.put(sessionId, session);
}
mSessionsChanged = true;
writeSessionsAsync();
return sessionId;

sessionId 是 service 管理集合的 key;stageDir 是后续写入的物理 owner。写 sessions 是异步持久化,目的是让未完成 session 能在重启后恢复。

2.4 openSession 访问 ​

java
@Override
public IPackageInstallerSession openSession(int sessionId) {
    try {
        return openSessionInternal(sessionId);
    } catch (IOException e) {
        throw ExceptionUtils.wrap(e);
    }
}

private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException {
    synchronized (mSessions) {
        final PackageInstallerSession session = mSessions.get(sessionId);
        if (session == null || !checkOpenSessionAccess(session)) {
            throw new SecurityException("No access to session " + sessionId);
        }
        return session;
    }
}

拿到 Binder session 仍要通过 owner/user 访问检查;sessionId 本身不是授权凭证。

3. 写入 stage ​

3.1 openWrite ​

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

java
@Override
public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
    assertCanWrite(false);
    try {
        return doWriteInternal(name, offsetBytes, lengthBytes, null);
    } catch (IOException e) {
        throw ExceptionUtils.wrap(e);
    }
}

openWrite() 的第一道保护是 assertCanWrite(false),它同时检查 caller、session 是否准备好、是否 sealed/destroyed。真正的文件创建在 doWriteInternal(),而不是 service 中完成。

3.2 文件描述符与偏移 ​

java
private ParcelFileDescriptor doWriteInternal(String name, long offsetBytes,
        long lengthBytes, @Nullable byte[] signature) throws IOException {
    synchronized (mLock) {
        assertPreparedAndNotSealedLocked("openWrite");
        if (mStageDirInUse) {
            throw new IllegalStateException("Stage dir already in use");
        }
    }
    final File target = new File(stageDir, name);
    final int flags = offsetBytes > 0
            ? O_WRONLY | O_APPEND : O_WRONLY | O_CREAT | O_TRUNC;
    final FileDescriptor fd = Os.open(target.getAbsolutePath(), flags, 0644);
    if (lengthBytes > 0) {
        Os.posix_fallocate(fd, 0, lengthBytes);
    }
    return new ParcelFileDescriptor(fd);
}

offset 大于 0 采用追加模式;否则创建或截断文件。lengthBytes 只用于预分配空间,不代表最终 APK 已完整写入。写入完成还需要关闭 FD,commit 时 session 会检查是否仍有打开的 transfer。

3.3 addFile 与 split ​

multi-package session 通过 parent/child session 组织多个 APK;每个 child 的 stage 仍由自己的 PackageInstallerSession 持有。base.apk、split APK 或 data loader 文件在 seal 前分别写入,再由 PackageLite 轻量解析确定 package 结构。

4. Commit 与封存 ​

4.1 commit 入口 ​

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

java
@Override
public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
    assertNotChild("commit");
    boolean immutableCheck = CompatChanges.isChangeEnabled(
            THROW_EXCEPTION_COMMIT_WITH_IMMUTABLE_PENDING_INTENT,
            Binder.getCallingUid());
    if (immutableCheck && statusReceiver.isImmutable()) {
        throw new IllegalArgumentException(
                "The commit() status receiver should come from a mutable PendingIntent");
    }

    if (!markAsSealed(statusReceiver, forTransfer)) {
        return;
    }
    if (isMultiPackage()) {
        synchronized (mLock) {
            boolean sealFailed = false;
            for (int i = mChildSessions.size() - 1; i >= 0; --i) {
                if (!mChildSessions.valueAt(i).markAsSealed(null, forTransfer)) {
                    sealFailed = true;
                }
            }
            if (sealFailed) {
                return;
            }
        }
    }
    dispatchSessionSealed();
}

root session 必须有 status receiver,child session 可传 null;multi-package commit 会封存所有 child。commit 返回只表示封存请求被接受,后续校验和安装仍异步进行。

4.2 markAsSealed ​

java
private boolean markAsSealed(@Nullable IntentSender statusReceiver, boolean forTransfer) {
    Preconditions.checkState(statusReceiver != null || hasParentSessionId(),
            "statusReceiver can't be null for the root session");
    assertCallerIsOwnerOrRoot();

    synchronized (mLock) {
        assertPreparedAndNotDestroyedLocked("commit of session " + sessionId);
        assertNoWriteFileTransfersOpenLocked();
        setRemoteStatusReceiver(statusReceiver);
        if (mSealed) {
            return true;
        }
        try {
            sealLocked();
        } catch (PackageManagerException e) {
            return false;
        }
    }
    return true;
}

封存前必须没有打开的文件 transfer;重复 commit 可以更新 receiver 并直接返回 true。sealLocked() 即使抛错,session 仍可能已经进入 sealed 状态,调用方必须根据后续 session 状态处理。

4.3 sealLocked ​

java
@GuardedBy("mLock")
private void sealLocked() throws PackageManagerException {
    try {
        assertNoWriteFileTransfersOpenLocked();
        assertPreparedAndNotDestroyedLocked("sealing of session " + sessionId);
        mSealed = true;
    } catch (Throwable e) {
        throw new PackageManagerException(
                INSTALL_FAILED_INTERNAL_ERROR, "Package is not valid", e);
    }
}

mSealed=true 是禁止写入的核心状态。它并不表示 APK 已验证,也不表示 package 已安装;验证在 sealed 消息之后进行。

5. Sealed 校验 ​

5.1 持久化 sealed ​

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

java
private void dispatchSessionSealed() {
    mHandler.obtainMessage(MSG_ON_SESSION_SEALED).sendToTarget();
}

private void handleSessionSealed() {
    assertSealed("dispatchSessionSealed");
    // Persist sealed state before hard links or install work can begin.
    mCallback.onSessionSealedBlocking(this);
    dispatchStreamValidateAndCommit();
}

service 回调 onSessionSealedBlocking() 同步写入 session 状态,确保 sealed 后不会因为进程重启而恢复成可写。随后才进入 stream validate and commit。

5.2 stageDirInUse ​

java
@GuardedBy("mLock")
private void markStageDirInUseLocked() throws PackageManagerException {
    if (mDestroyed) {
        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
                "Session destroyed");
    }
    // Prevent abandon() from deleting staging files during install.
    mStageDirInUse = true;
}

创建 InstallingSession 或开始安装前设置 mStageDirInUse。此后即使客户端调用 abandon,session 也不会立刻删除正在使用的 stage;等验证/安装结束后再执行 pending abandon callback。

5.3 校验与安装 ​

java
private CompletableFuture<Void> install() {
    synchronized (mMetrics) {
        mMetrics.onInternalInstallationStarted();
    }
    List<CompletableFuture<InstallResult>> futures = installNonStaged();
    CompletableFuture<InstallResult>[] array = new CompletableFuture[futures.size()];
    return CompletableFuture.allOf(futures.toArray(array)).whenComplete((r, t) -> {
        if (t == null) {
            setSessionApplied();
        } else {
            setSessionFailed(t);
        }
    });
}

校验通过后,普通 session 进入 installNonStaged();所有 session/child future 完成后才把 root 标记为 applied 或 failed。这里的 future 完成是客户端 session 状态,和 InstallPackageHelper 的 commit 状态仍不同。

6. 交接安装任务 ​

6.1 创建任务 ​

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 UserHandle user = (params.installFlags & PackageManager.INSTALL_ALL_USERS) != 0
            ? UserHandle.ALL : new UserHandle(userId);
    if (!isMultiPackage() && !isApexSession()) {
        synchronized (mLock) {
            mPackageLite = getOrParsePackageLiteLocked(stageDir, /*flags=*/ 0);
        }
    }

只有 sealed session 才能生成内部安装任务;普通 APK 在这里补做 PackageLite 解析。multi-package parent 和 staged APEX 走特殊分支,不应强行读取成单个 APK。

6.2 observer 桥接 ​

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

PMS 内部 observer 把 InstallPackageHelper 的最终结果转换成 session future;成功完成 future,失败以 PackageManagerException 结束。客户端最终收到的 status receiver 由 session 再转发。

6.3 installNonStaged ​

java
private List<CompletableFuture<InstallResult>> installNonStaged() {
    try {
        List<CompletableFuture<InstallResult>> futures = new ArrayList<>();
        CompletableFuture<InstallResult> future = new CompletableFuture<>();
        futures.add(future);
        final InstallingSession installingSession = createInstallingSession(future);
        if (isMultiPackage()) {
            final List<PackageInstallerSession> childSessions = getChildSessions();
            List<InstallingSession> children = new ArrayList<>(childSessions.size());
            for (PackageInstallerSession child : childSessions) {
                future = new CompletableFuture<>();
                futures.add(future);
                final InstallingSession installingChild = child.createInstallingSession(future);
                if (installingChild != null) {
                    children.add(installingChild);
                }
            }
            if (!children.isEmpty()) {
                Objects.requireNonNull(installingSession).installStage(children);
            }
        } else if (installingSession != null) {
            installingSession.installStage();
        }
        return futures;
    } catch (PackageManagerException e) {
        return List.of(CompletableFuture.failedFuture(e));
    }
}

单包调用 InstallingSession.installStage();multi-package 为 parent 和 child 建立多个 future,由内部 MultiPackageInstallingSession 等待 child 复制结果后统一交给 PMS 安装管道。

7. abandon 与清理 ​

7.1 Abandon 终态 ​

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

java
@Override
public void abandon() {
    final Runnable callback;
    synchronized (mLock) {
        assertNotChild("abandon");
        assertCallerIsOwnerOrRootOrSystem();
        if (isInTerminalState()) {
            return;
        }
        mDestroyed = true;
        callback = mStageDirInUse ? null : destroyInternalLocked();
    }
    if (callback != null) {
        callback.run();
    }
}

终态 session 不需要重复 abandon;stage 正在使用时只记录 destroyed,延迟实际删除。root session 负责协调 child,child 不能被客户端独立 commit/abandon。

7.2 延迟 abandon ​

java
private boolean dispatchPendingAbandonCallback() {
    final Runnable callback;
    synchronized (mLock) {
        if (!mStageDirInUse) {
            return false;
        }
        mStageDirInUse = false;
        callback = mPendingAbandonCallback;
        mPendingAbandonCallback = null;
    }
    if (callback != null) {
        callback.run();
        return true;
    }
    return false;
}

验证/安装完成后清除 mStageDirInUse,再执行 pending abandon callback。这样 stage 删除不会与正在进行的 parse/copy/dexopt 竞争。

7.3 磁盘空间回收 ​

java
// PackageInstallerService.freeStageDirs()
if (session.isStagedSessionReady()) {
    // Keep a ready session.
} else if (!root.isDestroyed()) {
    // Aggressively close old sessions because storage is low.
    root.abandon();
}

低存储时 service 会主动 abandon 过旧 session,连同其 staging dirs 一起回收;正在安装或已达到可保留状态的 session 不应被随意删除。

8. 状态与时序 ​

8.1 状态机 ​

8.2 生效点 ​

事件立即发生延后发生
createSessionservice 注册 session/stagesessions XML 异步写入
openWrite返回 stage FD数据完整性由后续校验确认
commitsealed、禁止写入stream validate/install
createInstallingSessionstage 标记 in usePMS 安装阶段执行
install resultfuture 完成status receiver 通知/清理
abandon during install标记 destroyed安装结束后删 stage

9. 测试与诊断 ​

9.1 输入与断言 ​

输入断言证明范围
未授权 caller openSessionSecurityExceptionsession 访问
data loader 无权限createSession 失败API 边界
未关闭 write FD commitcommit 被拒绝封存前置
root session null receiver参数失败callback 契约
multi-package child 未封存parent commit 不继续child 协调
sealed 后 openWrite写入失败不可变状态
stage APK 无法解析validate/PackageLite 失败输入校验
单包安装一个 InstallingSession/future单包路径
multi-packagechild futures 全部完成才交接批量路径
安装中 abandonstage 不立即删除mStageDirInUse
安装完成后 abandon callbackstage/session 被清理延迟清理

测试要同时检查 session 状态、stage 文件、future 和最终 observer;只断言 statusReceiver 的成功/失败,无法定位是写入、封存、校验还是 PMS 安装阶段出错。

9.2 现场排查顺序 ​

  1. 根据 sessionId 在 PackageInstallerService.mSessions 确认 session 是否存在。
  2. 查看 mSealed、mCommitted、mStageDirInUse 和 mDestroyed。
  3. 检查 stage 目录中的 base/split 文件以及是否仍有打开 FD。
  4. 查看 markAsSealed() 和 stream validation 的错误。
  5. 确认 createInstallingSession() 是否成功生成 PackageLite。
  6. 若已交给 PMS,转查 InstallingSession/InstallRequest return code,而不是继续查 session 写入。
  7. 若安装状态已完成但目录未清理,检查 pending abandon、future completion 和 cleanup callback。

10. 源码路线 ​

建议按以下顺序阅读:

  1. PackageInstallerService.createSession()/createSessionInternal():caller、flags、session 注册。
  2. openSession():访问控制和 Binder 返回对象。
  3. PackageInstallerSession.openWrite()/doWriteInternal():stage 文件写入。
  4. commit()/markAsSealed()/sealLocked():封存与 multi-package child。
  5. handleSessionSealed() 和 stream validation:封存后的异步消息链。
  6. createInstallingSession()/installNonStaged():交给 InstallingSession。
  7. InstallingSession.installStage():进入 PMS 安装阶段。
  8. abandon()/dispatchPendingAbandonCallback():终态和延迟清理。

11. 设计收束 ​

Session 安装的真实链路是:

text
createSession
  -> stage 可写
  -> openWrite/write
  -> commit 封存
  -> sealed 状态持久化与流校验
  -> create InstallingSession
  -> InstallPackageHelper
  -> future/observer/statusReceiver

PackageInstallerSession 保证输入在 commit 后不可再变,InstallingSession 把 sealed stage 转成内部安装任务,InstallPackageHelper 才负责扫描、协调、dexopt 和 commit。mStageDirInUse 则把 abandon 与安装中的文件使用隔开。理解这三个 owner,才能准确区分“session 已提交但尚未安装”“PMS 已 commit 但客户端回调未到达”和“stage 清理被延迟”这三类常见现象。