Skip to content

ADB安装与权限管理

追踪 adb install 从 host 模式选择、APK传输到 PackageInstaller会话提交的完整路径,并解释多APK、用户范围、运行时权限和卸载语义。

基于android-17.0.0_r1
AndroidADBPackageInstallerAPK安装运行时权限

ADB安装与权限管理 ​

执行 adb install app.apk 时,ADB 并不负责解析 manifest、校验签名或决定应用 UID。ADB 的职责是选择传输模式、把安装参数和 APK 字节送到设备,并消费安装结果;真正拥有安装状态的是设备上的 Package Manager 和 Package Installer session。

同样,adb install -g app.apk 与 adb shell pm grant PACKAGE PERMISSION 都可能改变运行时权限,但它们进入系统的时机、作用范围和失败条件不同:-g 是 session 的安装 flag,pm grant 是针对已安装包、指定权限和用户的独立请求。

建议先阅读 ADB架构 和 ADB shell机制。本文从 packages/modules/adb 与 frameworks/base 的真实入口展开安装、卸载和调试场景中的运行时权限,不展开 APK 签名格式、Package Manager 全部扫描过程或应用内权限 API。若要观察安装前后的日志证据,可继续阅读 ADB logcat进阶。

1. 安装命令 ​

adb install app.apk 至少跨越四层:

层主要入口拥有的状态直接消费者
host 命令层client/commandline.cpp::adb_commandlineargv、目标 transportinstall_app
host 安装器client/adb_install.cpp安装模式、本地文件、fallbackADB service fd
shell 命令层PackageManagerShellCommandinstall options、session id、用户PackageInstaller
安装系统PackageInstallerService / PackageInstallerSessionstaged files、校验、commit、结果PMS、Permission Manager、应用数据目录

“ADB 返回 Success”意味着设备安装系统完成了对应 session,并不意味着应用一定能启动、所有权限都已授予,或所有用户都已安装该包。后续状态还受用户、组件启用状态、运行时权限和应用自身逻辑影响。

2. host 安装模式 ​

2.1 安装模式差异 ​

client/adb_install.cpp 定义 INSTALL_PUSH、INSTALL_STREAM、INSTALL_INCREMENTAL 和默认选择。它们描述 APK 字节怎样到设备,不代表设备侧存在四个不同的包管理器。

host 模式字节路径设备侧入口主要特点
push先同步到 /data/local/tmppm install <path>兼容旧设备,产生临时文件
streaming本地 APK 直接写入远端 fdcmd package install -S SIZE -不要求完整 APK 先落入临时路径
incremental先提供签名/索引和部分块package install-incrementalPackage Manager 可在按需读取块的同时推进安装
fast deploy计算并传送 patch安装 agent + package command面向开发迭代,不适用于所有包类型

best_install_mode() 查询 transport feature。设备支持 cmd 时优先 streaming,否则退回 push。incremental 还会检查 abb_exec feature、host 环境变量和设备全局设置;显式 --incremental 失败时不会自动假装成功,默认选择 incremental 时才可以按计算出的 fallback 转入普通模式。

2.2 实现入口 ​

--streaming、--no-streaming、--incremental、--fastdeploy 首先由 host ADB 消费;-r、-t、-d、-g、--user 等参数会继续传给设备 Package Manager。

这一区分可以解释为什么某些错误由 host 立即输出,例如“文件不存在”或“设备不支持 streaming”;另一些错误要等设备返回,例如签名不匹配、版本降级被拒绝、用户不存在或存储不足。

安装参数属于不同处理层

阅读命令帮助时要问“谁消费这个参数”。改变 APK 传输方式的参数通常属于 host ADB;改变 session policy、目标用户和包校验规则的参数属于设备 Package Manager。

3. 流式安装 ​

3.1 host约束 ​

install_app_streamed() 先检查文件扩展名、设备是否支持 APEX,再对本地文件执行 stat() 和 open()。随后构造 package command,并覆盖用户可能传入的 -S:

text
package install ... -S <实际文件大小>

streaming 输入使用 - 表示 stdin。设备侧 PackageManagerShellCommand::doRunInstall() 明确要求 stdin 安装必须给出正数大小,否则 session 无法提前规划空间和数据边界。

3.2 安装命令入口 ​

host 支持 abb_exec 时,参数以结构化 argv 形式发送;不支持时,ADB 拼接 exec:cmd package 并使用 escape_arg() 处理 shell 参数。两条路径最终都进入设备上的 package shell command,但前者避免把参数重新交给 shell 字符串解析。

send_command() 返回远端 fd,copy_to_file() 把本地 APK 全部写入该 fd。写完后 host 通过 read_status_line() 等待设备返回文本;只有以 Success 开头才被视为安装成功。

源码文件:packages/modules/adb/client/adb_install.cpp

相关函数/类型:install_app_streamed

cpp
struct stat sb;
if (stat(file, &sb) == -1) {
    perror_exit("failed to stat %s", file);
}
unique_fd local_fd(adb_open(file, O_RDONLY | O_CLOEXEC));
if (local_fd < 0) {
    perror_exit("failed to open %s", file);
}

std::vector<std::string> cmd_args = {use_abb_exec ? "package" : "exec:cmd package"};
// ... 复制除 APK 路径外的参数
cmd_args.push_back("-S");
cmd_args.push_back(android::base::StringPrintf("%" PRIu64,
        static_cast<uint64_t>(sb.st_size)));

unique_fd remote_fd = send_command(cmd_args, &error);
if (remote_fd < 0) error_exit("connect error for write: %s", error.c_str());
if (!copy_to_file(local_fd.get(), remote_fd.get())) {
    perror_exit("failed to install: copy_to_file: %s", file);
}
read_status_line(remote_fd.get(), buf, sizeof(buf));
if (strncmp("Success", buf, 7) != 0) {
    error_exit("failed to install %s: %s", file, buf);
}

host 在发送字节前用 stat 得到大小,并把 -S 放在参数末尾覆盖用户输入;这正是设备侧 streaming session 能预先知道输入边界的原因。Success 只来自设备命令的最终状态行,不能把本地 copy_to_file 成功 误当作安装提交成功。

3.3 Success时间点 ​

普通 APK session 的 doRunInstall() 在 doCommitSession() 成功后输出 Success。如果是 staged session,命令还可能等待 session ready,并提示重启后应用;因此“命令成功”与“新代码已在当前进程运行”不是同一个时间点。

4. push install ​

旧式 install_app_legacy() 从参数中找到最后一个 APK,把目标路径改成:

text
/data/local/tmp/<apk basename>

随后 do_sync_push() 先完成文件同步,pm_command() 再执行设备安装,最后 delete_device_file() 删除临时 APK。push 成功只代表传输完成;pm install 仍可能在签名、版本、ABI、空间或策略阶段失败。

临时路径属于 shell 可写的中转区,不是应用最终安装目录。Package Manager 会从中读取包并建立自己的 code/data 状态。调试时不要把 /data/local/tmp/app.apk 的存在当作“应用已安装”,也不要把它与 /data/app/... 的最终 code path 混为一谈。

4.1 push三个结果 ​

阶段成功标准失败后的状态
sync push临时 APK 完整写入不应调用 Package Manager;可能需要清理残留
pm installsession commit 返回成功包状态不应部分替换,失败信息由 PMS 给出
临时清理中转文件被删除安装结果可能成功,但留下占用空间的临时文件

streaming 省去了显式临时 APK,但没有绕过 Package Installer session,也不会降低签名和权限检查。

5. 安装session ​

5.1 doRunInstall() ​

Android 17 的 PackageManagerShellCommand::doRunInstall() 首先检查:

  • sys.boot_completed 是否为 true;
  • 目标用户是否存在;
  • stdin 输入是否提供大小;
  • APEX 是否错误地带有多个 split;
  • streaming、文件路径和 split 组合是否合法。

通过前置检查后,代码调用 doCreateSession() 得到 session id,再写入一个或多个文件,最后 doCommitSession()。局部变量 abandonSession 初始为 true,只有 commit 成功才改为 false;任何提前返回都在 finally 中尝试 abandon。

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

相关函数/类型:doRunInstall

java
if (!SystemProperties.getBoolean("sys.boot_completed", false)) {
    pw.println("Error: device is still booting.");
    return 1;
}
if (fromStdIn && params.sessionParams.sizeBytes == -1) {
    pw.println("Error: must either specify a package size or an APK file");
    return 1;
}

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;
        }
    }
    // ... 非 streaming 路径写入文件
    if (doCommitSession(sessionId, false /*logSuccess*/)
            != PackageInstaller.STATUS_SUCCESS) {
        return 1;
    }
    abandonSession = false;
} finally {
    if (abandonSession) {
        try {
            doAbandonSession(sessionId, false /*logSuccess*/);
        } catch (Exception ignore) {
        }
    }
}

session 是设备侧事务 owner:前置检查失败时根本没有 session;session 创建后任何写入或 commit 失败都会 沿 finally abandon。读者可以用不存在的用户或缺少 -S 的 stdin 安装分别触发两个前置错误,断言不会得到 成功 session;用一个合法测试 APK 断言 commit 成功后不再 abandon。这个实验覆盖 shell command 的状态边界, 不外推到所有签名、ABI 或厂商安装策略。

5.2 commit 语义 ​

PackageInstallerSession 持有 session 生命周期,commit 后还要进入包解析、签名和版本关系校验、空间与 ABI 处理、用户安装状态更新等路径。本文不展开 PMS 全部内部流程,但排错必须知道这些检查属于设备端,而不是 ADB 字节复制阶段。

常见 INSTALL_FAILED_* 可以按 owner 分组:

错误方向典型原因应查看的设备侧边界
包格式APK 无效、split 缺失、manifest 不兼容parsing / session validation
更新关系签名不同、版本降级、shared UID 不兼容reconcile / signing / version policy
资源环境空间不足、ABI 不支持、共享库缺失storage / native library / dependency
安全策略verifier 拒绝、用户限制、安装来源策略verification / user policy
session大小不符、write 中断、session 不存在PackageInstallerSession

6. 单 APK ​

6.1 单 APK ​

bash
adb install app.apk

对于普通完整 APK,host 建立一次安装命令,设备创建一个完整安装 session。-r 在 Android 17 shell parser 中保持替换现有应用的默认行为;使用 -R 才关闭 replaceExisting。更新仍必须满足签名、version code 和包名关系。

6.2 splitAPK约束 ​

bash
adb install-multiple base.apk split_config.arm64_v8a.apk split_config.zh.apk

install_multiple_app_streamed() 计算所有文件的总大小,先执行 install-create,再为每个 APK 执行 install-write,最后 install-commit。任一文件 stat、open、写入或结果读取失败,代码把 final command 改为 install-abandon。

split 不是独立应用。base 与配置 split 必须属于同一包、签名一致,并满足 split 依赖。只安装一个配置 split 不能生成可运行应用。

6.3 multi-package ​

adb install-multi-package 可把多个 APK/APEX package 组织在一个 multi-package parent session 下。源码先创建带 --multi-package 的父 session,再创建子 session 并添加到 parent;commit 的目标是整组事务。

它与 install-multiple 的区别是:前者处理多个 package,后者处理一个 package 的多个 APK。若命令中出现 APEX,host 会要求 staged 语义;旧式 push 设备不支持 multi-package。

不要交换 multiple 与 multi-package

install-multiple 解决一个应用的 base/split 组合;install-multi-package 解决多个应用或模块的原子组合。选错命令会让 session 的包边界和提交语义完全不同。

7. Session状态 ​

incremental install 并非“压缩后更快地 push”。client/incremental.cpp 构造 package install-incremental 请求,建立 Incremental Server 所需的数据索引和 block 服务。签名相关文件先传输,Package Manager 可以在文件块尚未全部到达时推进安装,并在运行时按需请求块。

install_app_incremental() 可以通过 --wait 等待 server 进程退出;默认模式失败时,host 可能退回 streaming 或 push。显式 --incremental 表达的是“必须使用 incremental”,不会静默改变为其他模式。

判断 incremental 的成功至少要区分:

  1. install command 已取得足够数据并返回成功;
  2. Incremental Server 仍在提供缺失块;
  3. 应用首次启动触发的块读取是否完成。

因此安装命令结束时间与所有 APK 字节落盘时间可能不同。排错时要记录是 package session 失败、Incremental Server 退出,还是运行时 block 请求无法满足。

8. 安装选项 ​

8.1 -r ​

选项设置的 session 意图仍然不能绕过
-r替换已有应用;Android 17 shell 默认允许 replace签名不一致、包名不同
-d请求允许 version code 降级产品和 debuggable 策略、签名关系
-t允许 testOnly 包其他安装校验与用户策略

这些选项不是“强制成功”。它们只设置 Package Manager 识别的 install flags,后续校验仍可以拒绝 session。

8.2 --user ​

PackageManagerShellCommand 的 install params 默认面向系统用户语义,并通过 translateUserId() 处理明确的 --user。设备在安装前检查用户是否存在。一个 package 的代码可能在系统中只保存一份,但安装/启用状态、数据目录与权限授予都具有用户维度。

因此下面两个问题必须分开:

  • package code 是否已进入系统;
  • package 是否为指定用户安装且具有该用户的数据和权限状态。

pm install-existing --user USER PACKAGE 是把系统已有 package 启用/安装给用户,不会重新从 host 传 APK。

8.3 instant、APEX ​

--instant 设置 instant app session;--full 明确选择完整应用。--apex 把 session 标记为 APEX,APEX 通常涉及 staged 和重启应用,不能与普通 split 规则等同。ADB streaming 代码也明确拒绝设备不支持 APEX feature 的目标,并拒绝 fast deploy APEX。

9. -g与运行时权限 ​

9.1 -g ​

bash
adb install -g app.apk

ADB host 不会枚举权限;它把 -g 传给设备。PackageManagerShellCommand::makeInstallParams() 将其转换为 INSTALL_GRANT_ALL_REQUESTED_PERMISSIONS。Package Installer 在安装过程中尝试授予该应用请求且符合条件的运行时权限。

“all” 的边界是应用 manifest 请求的、可由运行时授权机制授予的权限。它不意味着获得 signature、privileged、role、AppOp 或 SELinux 能力,也不能授予应用没有声明的权限。

9.2 pmgrant ​

bash
adb shell pm grant --user 0 com.example.app android.permission.CAMERA
adb shell pm revoke --user 0 com.example.app android.permission.CAMERA

runGrantRevokePermission() 解析用户、package 和 permission,确认包存在,然后调用 PermissionManagerService.grantRuntimePermission() 或 revokeRuntimePermission()。其状态键至少包含 package、permission 和 user;不同用户的授权不能由一次没有正确用户范围的观察替代。

9.3 user-set ​

pm set-permission-flags 和 clear-permission-flags 可处理 user-set、user-fixed、review-required 等 shell 支持的 flags。flag 描述权限状态如何形成、系统是否应再次询问或允许策略变化;它与 granted/denied 位不是同一字段。

调试时只看 granted=true 可能不够,还要判断是否存在固定状态、兼容撤销或策略限制。反过来,清除 flag 也不会自动把 denied 改成 granted。

10. 权限边界 ​

pm grant 的命令入口并不等于 shell 可以授予任意权限。Permission Manager 会检查:

  • package 是否请求了该 permission;
  • permission 是否属于可运行时授予的类型;
  • package 的 target SDK 和权限定义是否满足条件;
  • 用户、设备策略和 fixed flags 是否允许修改;
  • 权限是否需要 signature、privileged、role 或其他专属 owner。

例如 signature permission 依据签名关系授予,privileged permission 受系统映像和 allowlist 管理,AppOp 是另一套操作控制状态,SELinux 决定进程域能否访问内核/服务资源。它们都不能由一次 pm grant 统一解决。

不要把权限命令当成安全边界绕过工具

ADB shell 权限管理用于可控测试和诊断。signature、privileged、设备策略与 SELinux 限制属于不同安全层,强行修改系统状态可能导致测试结论失真,也可能破坏多用户和企业策略。

11. 卸载与数据 ​

11.1 adbuninstall ​

设备支持 streamed command 时,host 构造 cmd package uninstall;旧设备使用 pm uninstall。最终删除 package 和用户状态的是 Package Manager,不是 ADB 删除 /data/app 目录。

11.2 -k特殊保护 ​

adb_install.cpp 对 adb uninstall -k 主动拒绝,并提示若确实要保留 data/cache,应显式运行设备 shell 中的 cmd package uninstall -k。原因是保留数据后,普通 ADB 命令没有独立接口清理这份残留状态;要恢复访问通常必须用同签名包重新安装后再完整卸载。

这说明 -k 不是“安全卸载”。它保留的是与 package 身份和签名关系相关的数据。若后续 APK 签名不同,系统不能把旧数据交给新应用。

11.3 多用户卸载 ​

针对某个用户卸载可能只改变该用户的 installed 状态;系统仍可能为其他用户保留包。判断卸载结果时,应同时确认 package code 是否还存在,以及目标用户的 package 状态是否已删除。

12. 安装失败 ​

现象最可能的层首个源码入口下一步证据
host 提示找不到 APKhost installerinstall_app_streamed / legacy路径、后缀、stat/open
不支持 streaming/incrementalfeature/模式选择calculate_install_modetransport features、是否显式强制模式
push 失败sync transportdo_sync_pushtransport、空间、临时目录
must specify package sizepackage shelldoRunInstallstdin 与 -S 是否匹配
user doesn't existpackage shell/user managerdoRunInstall--user、用户列表
INSTALL_FAILED_UPDATE_INCOMPATIBLEPMS reconcileReconcilePackageUtils包名、签名、已有版本
INSTALL_FAILED_VERSION_DOWNGRADEversion policyPackageManagerServiceUtilsversionCode、-d、debuggable 条件
grant 失败Permission ManagerrunGrantRevokePermission是否请求、权限类型、用户、fixed flags
install-multiple 写到一半失败session writeinstall_multiple_app_streamed哪个 split、大小、session abandon

12.1 安装选项排查 ​

把所有选项一起添加会丢失证据:你无法判断原始失败是签名、版本、testOnly 还是权限状态。更可靠的方法是保留原命令和完整 Failure,然后只改变一个有源码依据的条件。

12.2 安装失败测试 ​

一次完整验证至少包含:

  1. 安装命令返回成功;
  2. pm path PACKAGE 能定位 code path;
  3. 指定用户能查询到 package;
  4. versionCode、签名预期和 split 列表正确;
  5. 运行时权限状态符合测试前置条件;
  6. 启动目标组件并观察实际进程行为。

这些检查分别验证 package code、用户状态、权限状态和运行状态,不能互相替代。

13. 安装测试 ​

13.1 权限测试 ​

packages/modules/adb/test_device.py::test_install_argument_escaping 构造包含特殊字符的安装参数,验证 ADB 经 shell command 传递参数时不会错误分词。这能支持 escape_arg() 路径的行为,但不能证明任意 APK 都通过 Package Manager 校验。

ADB 的设备测试还覆盖实际 install/uninstall 场景。这类测试能验证 client、transport 和设备 package service 的组合,但结果仍取决于测试 APK 与测试目标的构建属性。

13.2 安装结果测试 ​

本文的重要边界也直接由失败分支证明:

输入源码断言证明的结论
streaming stdin 没有 -SdoRunInstall 返回 error大小是协议/session 前置条件
目标 user 不存在安装前返回 Failure用户校验先于 session 写入
split write 失败final command 选择 abandon多 APK 不应提交半成品 session
显式 incremental 不支持host 直接报错强制模式不会静默 fallback
pm grant 包不存在输出 package not found权限状态依附已安装 package/user
adb uninstall -khost 拒绝并提示显式 shell 命令数据保留需要使用者明确承担恢复边界

测试和失败分支共同告诉我们“代码允许什么、拒绝什么”。它们不能替代目标 APK 的签名、ABI、空间和产品策略验证。

14. 源码路径 ​

如果要在 Android 17 源码中复现本文,建议按下面的顺序阅读:

  1. packages/modules/adb/client/commandline.cpp:确认 install、install-multiple、install-multi-package、uninstall 如何分派;
  2. client/adb_install.cpp::install_app():确认 mode、fallback 和 fast deploy 的 owner;
  3. install_app_streamed() 与 install_app_legacy():比较 fd streaming 与临时文件 push;
  4. install_multiple_app_streamed():跟踪 create、write、commit/abandon;
  5. client/incremental.cpp::install():理解 Incremental Server 与 package install-incremental;
  6. frameworks/base/.../PackageManagerShellCommand::doRunInstall():确认用户、输入、session 和 finally 清理;
  7. makeInstallParams():把 -r/-d/-t/-g/--user 映射为 session 参数;
  8. runGrantRevokePermission() 与 PermissionManagerService:确认权限状态的 package/user 维度。

这条路径先建立数据传输,再进入设备事务,最后处理权限状态,便于逐层验证,不需要一开始进入庞大的 PackageManagerService 全部代码。

15. 分层实验 ​

15.1 包身份验证 ​

安装测试 APK 前,先从 APK 元数据记录 package name、versionCode、min/target SDK、请求权限和签名摘要。这个顺序看似慢,却能避免把“更新失败”误判成“ADB 传输失败”:-r 只表达 replace 意图,不能使不同签名的包成为合法更新。

一次有边界的实验可以分成四次安装:

bash
adb install app.apk
adb install -r app.apk
adb install -r -d app.apk
adb install -r -t app-test.apk

每次只增加一个条件,并保存完整输出。第一条验证全新安装;第二条验证同签名更新;第三条只在 debug/产品策略允许时观察降级分支;第四条验证 testOnly 包的允许条件。若第二条失败而第三条成功,证据指向 version policy,而不是文件传输。

15.2 split session ​

对 split APK,故意让其中一个文件路径错误,可以观察 host 在 install-write 阶段失败并发出 install-abandon。然后使用正确文件重试,并在成功后检查包的 split 列表。这个实验能够证明“部分写入不会直接变成已安装应用”,也能帮助区分 host 本地 stat/open 失败和设备 session write 失败。

不要只看最终的应用图标。启动器缓存、当前用户和组件启用状态都可能让 UI 观察滞后;pm path、包信息和 session 输出更接近 Package Manager 的真实状态。

15.3 权限实验约束 ​

对一个声明了运行时权限的调试包,先查询用户 0 的权限,再在用户 0 执行 grant/revoke:

bash
adb shell cmd package list packages --user 0 | grep com.example.app
adb shell pm grant --user 0 com.example.app android.permission.CAMERA
adb shell pm revoke --user 0 com.example.app android.permission.CAMERA

如果设备存在第二个用户,重复查询并比较两个用户的状态。一次没有 --user 的命令不能作为多用户结论;系统用户、当前用户和 shell 命令默认用户之间存在转换逻辑。若 grant 失败,先确认 manifest 请求和权限 protection level,再检查 fixed flag,而不是不断重复命令。

15.4 卸载实验 ​

完整卸载前记录 pm path、应用数据目录和用户状态;执行普通 adb uninstall 后再次查询,确认 code 和目标用户状态均已消失。若显式使用设备 shell 的 cmd package uninstall -k 保留数据,再安装同签名包,观察旧数据是否恢复;随后执行不保留数据的卸载完成清理。

这个实验说明 -k 的影响不在 ADB 临时文件,而在 Package Manager 的用户数据状态。换签名重装失败时,失败原因是 package identity 和签名关系,而不是 -k 本身把 APK 损坏。

15.5 记录每层证据 ​

一个可复用的安装记录至少包含:

层记录内容
host完整命令、ADB 版本、选中的 install mode、APK 本地大小
transportserial、online 状态、是否使用 abb exec、是否发生 fallback
sessionsession id、create/write/commit 或 abandon 结果
packagepackage name、version、签名、split、code path
user目标 user、installed/enabled 状态、数据目录
permissionrequested、granted/revoked、flags、AppOp/策略相关现象
runtime组件解析、进程启动和实际 API 行为

缺少其中任一层,都可能把上游成功误写成下游成功。例如 session commit 成功只能证明 package 系统接受了安装事务;它不证明用户 10 已安装,也不证明相机权限和 SELinux 都允许应用打开设备。

16. 状态分层 ​

adb install 的最终心智模型可以压缩为三个相邻但独立的状态:

  • 安装状态:PackageInstaller session 是否成功提交,代码与包设置是否建立;
  • 用户权限状态:指定 user 下哪些 runtime permissions 被授予、撤销或固定;
  • 运行状态:组件能否解析、进程能否启动、AppOp/SELinux/设备策略是否允许实际操作。

-g 只能在安装阶段请求一组符合条件的运行时权限,pm grant/revoke 只能改变 Permission Manager 允许修改的权限状态,二者都不能证明应用最终操作成功。反过来,应用启动失败也不能直接推出 APK 安装失败。

遇到问题时,从 host 文件和安装模式开始,沿 ADB service 到 Package Installer session,再检查 package/user/permission,最后进入应用运行路径。每一层都保留自己的 Success、Failure 和 cleanup,这才是 Android 17 中 ADB 安装与权限管理的完整边界。