Skip to content

ADB 安装流程

追踪 adb install 到 cmd package、PackageInstaller session 和 PMS 安装结果的完整调用链。

基于android-17.0.0_r1
AndroidPackageManagerServiceADBPackageInstallerPackageManagerShellCommand源码阅读

ADB 安装流程 ​

本文承接 Session 安装、InstallingSession 会话、安装前检查 和 安装时 dexopt。主题是开发者输入的 adb install 如何在设备侧变成 cmd package install,再通过 shell 命令、PackageInstaller session 和 PMS 安装管道完成。

本文不重复 PackageInstaller 的一般 API,也不展开 adb 主机端传输协议的所有实现。重点放在 Android 17 源码中可以直接核对的设备侧路径:命令分发、stdin/文件/split 分叉、shell caller 归一化、session 写入、commit 等待和最终输出。

1. 端到端主线 ​

1.1 调用关系 ​

adb 主机端和 adbd 负责把命令送到设备;system_server 内真正解析安装参数的是 PackageManagerShellCommand。从该点开始,ADB 安装与其他 session 安装共用 PackageInstaller/PMS 管道,但 caller UID 和若干 flags 已经体现 shell 来源。

1.2 三种输入形态 ​

输入cmd package 处理stage 写入方式
单 APK 路径runInstall()doWriteSplits()
stdin (-)runInstall() + -SdoWriteSplit() 从 stdin 读
streaming/incrementalrunInstall-streaming/install-incrementaldoAddFiles() + DataLoader
多 APKinstall-create/write/commit 或 runInstall 多参数多次写入 base/split

2. 命令分发 ​

2.1 onCommand ​

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

java
@Override
public int onCommand(String cmd) {
    if (cmd == null) {
        return handleDefaultCommands(cmd);
    }
    try {
        switch (cmd) {
            case "install":
                return runInstall();
            case "install-streaming":
                return runStreamingInstall();
            case "install-incremental":
                return runIncrementalInstall();
            case "install-create":
                return runInstallCreate();
            case "install-write":
                return runInstallWrite();
            case "install-commit":
                return runInstallCommit();
            case "install-abandon":
            case "install-destroy":
                return runInstallAbandon();
            default:
                return handleDefaultCommands(cmd);
        }
    } catch (RemoteException e) {
        getOutPrintWriter().println("Remote exception: " + e);
        return -1;
    }
}

cmd package 的单步 install 与分步 session 命令在同一个 ShellCommand 中分发。adb install 通常使用 install,而 adb install-multiple 等场景可以显式使用 create/write/commit 三步。

2.2 runInstall 家族 ​

java
private int runInstall() throws RemoteException {
    return doRunInstall(makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS));
}

private int runStreamingInstall() throws RemoteException {
    final InstallParams params = makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS);
    if (params.sessionParams.dataLoaderParams == null) {
        params.sessionParams.setDataLoaderParams(
                PackageManagerShellCommandDataLoader.getStreamingDataLoaderParams(this));
    }
    return doRunInstall(params);
}

private int runIncrementalInstall() throws RemoteException {
    final InstallParams params = makeInstallParams(UNSUPPORTED_INSTALL_CMD_OPTS);
    if (params.sessionParams.dataLoaderParams == null) {
        params.sessionParams.setDataLoaderParams(
                PackageManagerShellCommandDataLoader.getIncrementalDataLoaderParams(this));
    }
    return doRunInstall(params);
}

三种命令最终都进入 doRunInstall();差别在于是否预先给 SessionParams 设置 DataLoader 参数。streaming/incremental 不是另一套 PMS 安装实现,而是同一 session 管道的输入加载分支。

3. doRunInstall ​

3.1 启动与用户检查 ​

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

java
private int doRunInstall(final InstallParams params) throws RemoteException {
    final PrintWriter pw = getOutPrintWriter();
    if (!SystemProperties.getBoolean("sys.boot_completed", false)) {
        pw.println("Error: device is still booting.");
        return 1;
    }

    final int requestUserId = params.userId;
    if (requestUserId != UserHandle.USER_ALL
            && requestUserId != UserHandle.USER_CURRENT) {
        UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
        UserInfo userInfo = umi.getUserInfo(requestUserId);
        if (userInfo == null) {
            pw.println("Failure [user " + requestUserId + " doesn't exist]");
            return 1;
        }
    }

设备尚未 boot completed 或指定用户不存在时,命令在创建 session 前直接失败。此时不会产生 stage,也不会触发 PackageInstallerService。

3.2 输入约束 ​

java
final boolean isStreaming = params.sessionParams.dataLoaderParams != null;
final boolean isApex =
        (params.sessionParams.installFlags & PackageManager.INSTALL_APEX) != 0;
final boolean installArchived =
        (params.sessionParams.installFlags & PackageManager.INSTALL_ARCHIVED) != 0;
final ArrayList<String> args = getRemainingArgs();
final boolean fromStdIn = args.isEmpty() || STDIN_PATH.equals(args.get(0));
final boolean hasSplits = args.size() > 1;

if (fromStdIn && params.sessionParams.sizeBytes == -1) {
    pw.println("Error: must either specify a package size or an APK file");
    return 1;
}
if (isApex && hasSplits) {
    pw.println("Error: can't specify SPLIT(s) for APEX");
    return 1;
}
if (installArchived && hasSplits) {
    pw.println("Error: can't have SPLIT(s) for Archival install");
    return 1;
}

stdin 必须配合 -S 大小;APEX 和归档安装不接受多 split。命令层先拒绝这些组合,避免把无效输入送入 session。

3.3 单步事务外壳 ​

java
final int sessionId = doCreateSession(params.sessionParams,
        params.installerPackageName, params.userId);
boolean abandonSession = true;
try {
    if (isStreaming) {
        if (doAddFiles(sessionId, args, params.sessionParams.sizeBytes,
                isApex, installArchived) != PackageInstaller.STATUS_SUCCESS) {
            return 1;
        }
    } else if (doWriteSplits(sessionId, args, params.sessionParams.sizeBytes,
            isApex) != PackageInstaller.STATUS_SUCCESS) {
        return 1;
    }
    if (doCommitSession(sessionId, false /*logSuccess*/)
            != PackageInstaller.STATUS_SUCCESS) {
        return 1;
    }
    abandonSession = false;
    pw.println("Success");
    return 0;
} finally {
    if (abandonSession) {
        try {
            doAbandonSession(sessionId, false /*logSuccess*/);
        } catch (Exception ignore) {
        }
    }
}

单步命令用 abandonSession 保护 session:写入或 commit 命令返回失败时主动 abandon;commit 调用被接受后才停止自动 abandon。对 staged session,commit 返回后可能只是 ready,命令还可以按 timeout 轮询 ready 状态。

4. Session 与 Shell ​

4.1 doCreateSession ​

java
private int doCreateSession(SessionParams params, String installerPackageName, int userId)
        throws RemoteException {
    if (userId == UserHandle.USER_ALL) {
        params.installFlags |= PackageManager.INSTALL_ALL_USERS;
    }
    final int translatedUserId =
            translateUserId(userId, UserHandle.USER_SYSTEM, "doCreateSession");
    return mInterface.getPackageInstaller().createSession(
            params, installerPackageName, null /*installerAttributionTag*/,
            translatedUserId);
}

Shell 命令通过 IPackageInstallerService Binder 创建 session;USER_ALL 先转换为安装 flag,实际 Binder 调用使用 system user 作为服务侧目标。

4.2 Service 归一化 ​

java
if (PackageManagerServiceUtils.isRootOrShell(callingUid)
        || PackageInstallerSession.isSystemDataLoaderInstallation(params)
        || PackageManagerServiceUtils.isAdoptedShell(callingUid, mContext)) {
    params.installFlags |= PackageManager.INSTALL_FROM_ADB;
    installerPackageName = SHELL_PACKAGE_NAME;
} else {
    params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
    params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
    params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
}

进入 service 后,shell/root caller 被标记为 INSTALL_FROM_ADB,并使用 shell installer name。这个标志影响后续 test-only、downgrade、验证和日志策略;它不是 adb 主机端自行写入的字符串。

5. 写入 APK ​

5.1 stdin 与本地文件 ​

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

java
private int doAddFiles(int sessionId, ArrayList<String> args, long sessionSizeBytes,
        boolean isApex, boolean installArchived) throws RemoteException {
    PackageInstaller.Session session = null;
    try {
        session = new PackageInstaller.Session(
                mInterface.getPackageInstaller().openSession(sessionId));
        if (args.isEmpty() || STDIN_PATH.equals(args.get(0))) {
            final String name = "base" + RANDOM.nextInt()
                    + "." + (isApex ? "apex" : "apk");
            final long size = installArchived ? -1 : sessionSizeBytes;
            session.addFile(LOCATION_DATA_APP, name, size,
                    Metadata.forStdIn(name).toByteArray(), null);
            return 0;
        }

        for (String arg : args) {
            if (arg.indexOf(':') != -1) {
                if (processArgForStdin(arg, session) != 0) {
                    return 1;
                }
            } else {
                // Local file path is handled by the regular write helper.
                if (doWriteSplit(sessionId, arg, -1,
                        new File(arg).getName(), false) != 0) {
                    return 1;
                }
            }
        }
        return 0;
    } finally {
        IoUtils.closeQuietly(session);
    }
}

streaming 模式通过 addFile() 描述输入;普通本地路径最终仍写入 session stage。stdin 的 metadata 记录文件名和大小,供后续验证和 data loader 使用。

5.2 openInFile ​

java
private Pair<ParcelFileDescriptor, Long> openInFile(String inPath, long sizeBytes)
        throws IOException {
    final ParcelFileDescriptor fd;
    if (STDIN_PATH.equals(inPath)) {
        fd = ParcelFileDescriptor.dup(getInFileDescriptor());
    } else if (inPath != null) {
        fd = openFileForSystem(inPath, "r");
        if (fd == null) {
            return Pair.create(null, -1L);
        }
        sizeBytes = fd.getStatSize();
    } else {
        fd = ParcelFileDescriptor.dup(getInFileDescriptor());
    }
    if (sizeBytes <= 0) {
        getErrPrintWriter().println("Error: must specify an APK size");
        return Pair.create(fd, 1L);
    }
    return Pair.create(fd, sizeBytes);
}

stdin 通过 shell command 的输入 FD 复制;本地路径由 system 打开并读取 stat size。大小缺失或非正数时,命令在 session.write() 前失败。

5.3 doWriteSplit ​

java
private int doWriteSplit(int sessionId, String inPath, long sizeBytes,
        String splitName, boolean logSuccess) throws RemoteException {
    PackageInstaller.Session session = null;
    try {
        session = new PackageInstaller.Session(
                mInterface.getPackageInstaller().openSession(sessionId));
        final Pair<ParcelFileDescriptor, Long> fdWithSize =
                openInFile(inPath, sizeBytes);
        if (fdWithSize.first == null) {
            return fdWithSize.second.intValue();
        }
        final ParcelFileDescriptor fd = fdWithSize.first;
        session.write(splitName, 0, fdWithSize.second, fd);
        if (logSuccess) {
            getOutPrintWriter().println("Success: streamed "
                    + fdWithSize.second + " bytes");
        }
        return 0;
    } catch (IOException e) {
        getErrPrintWriter().println("Error: failed to write; " + e.getMessage());
        return 1;
    } finally {
        IoUtils.closeQuietly(session);
    }
}

每个输入文件都通过 Binder session 的 write() 进入 stage。这里的 splitName 是 session 内文件名,不是最终 Manifest split name;完整 Manifest 解析会在 PMS 后续阶段重新确认 split 关系。

5.4 多 APK 文件名 ​

java
private int doWriteSplits(int sessionId, ArrayList<String> splitPaths,
        long sessionSizeBytes, boolean isApex) throws RemoteException {
    final boolean multipleSplits = splitPaths.size() > 1;
    for (String splitPath : splitPaths) {
        String splitName = multipleSplits
                ? new File(splitPath).getName()
                : "base." + (isApex ? "apex" : "apk");
        if (doWriteSplit(sessionId, splitPath, sessionSizeBytes,
                splitName, false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
            return 1;
        }
    }
    return 0;
}

多个本地 APK 逐个写入同一个 session;单文件则统一命名为 base.apk/base.apex。真正的 base 与 split 识别依赖 APK Manifest 的 split 属性,而非只依赖命令行文件名。

6. Commit 与结果 ​

6.1 doCommitSession ​

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

java
private int doCommitSession(int sessionId, boolean logSuccess)
        throws RemoteException {
    final PrintWriter pw = getOutPrintWriter();
    PackageInstaller.Session session = null;
    try {
        session = new PackageInstaller.Session(
                mInterface.getPackageInstaller().openSession(sessionId));
        final LocalIntentReceiver receiver = new LocalIntentReceiver();
        session.commit(receiver.getIntentSender());
        if (!session.isStaged()) {
            final Intent result = receiver.getResult();
            int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                    PackageInstaller.STATUS_FAILURE);
            List<String> warnings = result.getStringArrayListExtra(
                    PackageInstaller.EXTRA_WARNINGS);
            if (status == PackageInstaller.STATUS_SUCCESS) {
                if (!ArrayUtils.isEmpty(warnings)) {
                    for (String warning : warnings) {
                        pw.println("Warning: " + warning);
                    }
                    status = PackageInstaller.STATUS_FAILURE;
                    pw.println("Completed with warning(s)");
                } else if (logSuccess) {
                    pw.println("Success");
                }
            } else {
                pw.println("Failure ["
                        + result.getStringExtra(
                                PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
            }
            return status;
        }
        // Staged result is checked by the caller after commit.
        if (logSuccess) {
            pw.println("Success");
        }
        return PackageInstaller.STATUS_SUCCESS;
    } finally {
        IoUtils.closeQuietly(session);
    }
}

普通 session 等待 LocalIntentReceiver 的结果;warnings 会被命令当作 failure 输出以提醒开发者。staged session 不在这里等待最终安装结果,由调用方轮询 ready/failed。

6.2 Staged 等待 ​

java
if (params.sessionParams.isStaged && params.stagedReadyTimeoutMs > 0) {
    return doWaitForStagedSessionReady(sessionId,
            params.stagedReadyTimeoutMs, pw);
}
pw.println("Success");
return 0;

adb install --staged 的命令成功通常只代表 session 已提交并进入 staged 状态;若指定 timeout,命令会进一步等待 SessionInfo.isStagedSessionReady() 或失败。

7. PMS 交接 ​

7.1 Session 交接 ​

7.2 Flags 下游影响 ​

INSTALL_FROM_ADB 会被 InstallPackageHelper 读取,用于 test-only、downgrade、verification 和日志等策略。命令层的 -r/-d/-t/-g/--staged 等选项最终都成为 SessionParams flags,再由 service 和 prepare 阶段分别解释。

8. 失败与清理 ​

8.1 命令层失败 ​

失败位置清理动作
boot/user/input 检查尚无 session,无需清理
createSession 失败service 不登记 session
write/addFile 失败doRunInstall finally abandon
commit 调用失败abandonSession 删除 stage
普通安装结果失败session/PMS 负责 code path 和状态清理
staged 恢复失败StagingManager/session 记录 failed

8.2 abandon 保护 ​

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

java
boolean abandonSession = true;
try {
    // write and commit
    abandonSession = false;
} finally {
    if (abandonSession) {
        try {
            doAbandonSession(sessionId, false /*logSuccess*/);
        } catch (Exception ignore) {
        }
    }
}

命令进程异常退出或写入失败时,finally 尽力 abandon。abandon 是否能立即删 stage 还取决于 PackageInstallerSession 的 mStageDirInUse;安装已经开始时,删除会延后到安全时机。

8.3 Warning 语义 ​

doCommitSession() 对普通 session 收到 warnings 时输出 warning 并返回 failure status,即使 PackageInstaller 内部 status 是 success。这是 shell 命令的用户体验策略,不代表 PMS commit 失败;诊断时要区分安装状态和命令退出码。

9. 测试与诊断 ​

9.1 输入与断言 ​

输入断言覆盖范围
boot 未完成命令返回 1,未创建 session前置检查
stdin 无 -S命令拒绝输入大小
APEX + 多文件命令拒绝类型约束
adb install app.apkcreate/write/commit 顺序正确单步路径
多 APK同一 session 多次 writesplit 输入
install-create/write/commit分步命令共享 sessionId手动 session
write 失败finally abandon命令清理
普通 commit warning输出 warning,命令返回 failure状态/退出码分离
staged commit不等待最终 appliedstaged 边界
incremental设置 DataLoader 参数输入加载分支
shell callerINSTALL_FROM_ADB 被设置service 归一化

测试应同时观察 shell 输出、session 状态、stage 文件和 PMS 最终结果;只检查 adb 退出码会遗漏“安装成功但有 warning”或 staged 仍在等待重启的情况。

9.2 现场排查顺序 ​

  1. 在 host 侧确认发送的是单步 install 还是分步 create/write/commit。
  2. 在设备侧确认 cmd package 是否通过 onCommand() 进入正确分支。
  3. 检查 doRunInstall() 的输入模式、用户和 size 参数。
  4. 查看 PackageInstallerService 是否创建 session,flags 是否包含 INSTALL_FROM_ADB。
  5. 检查 session stage 中 base/split 文件是否写完整、是否已经 sealed。
  6. 普通 session 查看 LocalIntentReceiver status/warnings;staged session 查看 ready/failed。
  7. 已交给 PMS 后转查 InstallingSession、InstallRequest 和 InstallPackageHelper。

10. 源码路线 ​

建议按以下顺序阅读:

  1. PackageManagerShellCommand.onCommand():命令分发。
  2. runInstall()/doRunInstall():单步安装外壳和失败 abandon。
  3. doCreateSession():ShellCommand 到 Binder service。
  4. PackageInstallerService.createSessionInternal():shell caller 与 flags 归一化。
  5. doAddFiles()/doWriteSplit()/doWriteSplits():stdin、本地文件和多 APK。
  6. PackageInstallerSession.write()/commit():stage 写入与封存。
  7. doCommitSession():普通/staged 结果输出差异。
  8. InstallingSession 与 InstallPackageHelper:进入 PMS 安装管道。

11. 设计收束 ​

ADB 安装的真实链路是:

text
adb client
  -> adbd shell / cmd package
  -> PackageManagerShellCommand
  -> PackageInstallerService session
  -> stage write + commit
  -> InstallingSession / InstallRequest
  -> InstallPackageHelper
  -> PMS 状态更新与结果回调

ADB 的特殊性主要在入口身份和输入传输:shell caller 被标记为 INSTALL_FROM_ADB,stdin/多 APK/streaming 由命令层选择不同写入方式;一旦 session commit 并交给 PMS,后续 prepare、scan、reconcile、dexopt 和 commit 与其他安装路径共享。理解命令退出码、session status、PMS return code 和 staged ready 的层次差异,才能正确分析“命令失败但包已安装”“命令成功但 staged 尚未应用”和“写入失败后 stage 未清理”等现象。