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 registered | PackageInstallerService.mSessions | 可被 sessionId 找到 |
| writable | PackageInstallerSession | stage 可写 |
| sealed | PackageInstallerSession.mSealed | 禁止继续写入 |
| stage in use | mStageDirInUse | abandon 不再删除 stage |
| internal install | InstallingSession/InstallRequest | 进入 PMS 安装阶段 |
| applied/failed | PackageInstallerSession future/callback | 对客户端报告结果 |
2. Service 管理
2.1 创建入口
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageInstallerService.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
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 分配与注册
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 访问
@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
@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 文件描述符与偏移
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
@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
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
@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
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
@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 校验与安装
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 创建任务
@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 桥接
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
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
@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
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 磁盘空间回收
// 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 生效点
| 事件 | 立即发生 | 延后发生 |
|---|---|---|
| createSession | service 注册 session/stage | sessions XML 异步写入 |
| openWrite | 返回 stage FD | 数据完整性由后续校验确认 |
| commit | sealed、禁止写入 | stream validate/install |
| createInstallingSession | stage 标记 in use | PMS 安装阶段执行 |
| install result | future 完成 | status receiver 通知/清理 |
| abandon during install | 标记 destroyed | 安装结束后删 stage |
9. 测试与诊断
9.1 输入与断言
| 输入 | 断言 | 证明范围 |
|---|---|---|
| 未授权 caller openSession | SecurityException | session 访问 |
| data loader 无权限 | createSession 失败 | API 边界 |
| 未关闭 write FD commit | commit 被拒绝 | 封存前置 |
| root session null receiver | 参数失败 | callback 契约 |
| multi-package child 未封存 | parent commit 不继续 | child 协调 |
| sealed 后 openWrite | 写入失败 | 不可变状态 |
| stage APK 无法解析 | validate/PackageLite 失败 | 输入校验 |
| 单包安装 | 一个 InstallingSession/future | 单包路径 |
| multi-package | child futures 全部完成才交接 | 批量路径 |
| 安装中 abandon | stage 不立即删除 | mStageDirInUse |
| 安装完成后 abandon callback | stage/session 被清理 | 延迟清理 |
测试要同时检查 session 状态、stage 文件、future 和最终 observer;只断言 statusReceiver 的成功/失败,无法定位是写入、封存、校验还是 PMS 安装阶段出错。
9.2 现场排查顺序
- 根据 sessionId 在
PackageInstallerService.mSessions确认 session 是否存在。 - 查看
mSealed、mCommitted、mStageDirInUse和mDestroyed。 - 检查 stage 目录中的 base/split 文件以及是否仍有打开 FD。
- 查看
markAsSealed()和 stream validation 的错误。 - 确认
createInstallingSession()是否成功生成PackageLite。 - 若已交给 PMS,转查 InstallingSession/InstallRequest return code,而不是继续查 session 写入。
- 若安装状态已完成但目录未清理,检查 pending abandon、future completion 和 cleanup callback。
10. 源码路线
建议按以下顺序阅读:
PackageInstallerService.createSession()/createSessionInternal():caller、flags、session 注册。openSession():访问控制和 Binder 返回对象。PackageInstallerSession.openWrite()/doWriteInternal():stage 文件写入。commit()/markAsSealed()/sealLocked():封存与 multi-package child。handleSessionSealed()和 stream validation:封存后的异步消息链。createInstallingSession()/installNonStaged():交给 InstallingSession。InstallingSession.installStage():进入 PMS 安装阶段。abandon()/dispatchPendingAbandonCallback():终态和延迟清理。
11. 设计收束
Session 安装的真实链路是:
createSession
-> stage 可写
-> openWrite/write
-> commit 封存
-> sealed 状态持久化与流校验
-> create InstallingSession
-> InstallPackageHelper
-> future/observer/statusReceiverPackageInstallerSession 保证输入在 commit 后不可再变,InstallingSession 把 sealed stage 转成内部安装任务,InstallPackageHelper 才负责扫描、协调、dexopt 和 commit。mStageDirInUse 则把 abandon 与安装中的文件使用隔开。理解这三个 owner,才能准确区分“session 已提交但尚未安装”“PMS 已 commit 但客户端回调未到达”和“stage 清理被延迟”这三类常见现象。
