系统控制命令
am、pm、wm、svc 看起来像四套独立的 shell 工具,实际上它们大多只是把参数送入 system_server 中不同的 Binder 服务。真正决定行为的不是 wrapper 的名字,而是后面的 ActivityManagerShellCommand、PackageManagerShellCommand、WindowManagerShellCommand 和 各个 service command。
本文面向已经读过 ADB架构 和 ADB shell机制 的读者:你需要知道 adbd 能启动 shell 子进程,并能区分 shell 通道与 Binder service;不要求先读 ActivityManagerService 或 PackageManagerService 的全部实现。本文不展开每个命令的所有选项,也不把 服务的 dump 输出当作稳定 API;诊断输出见 dumpsys诊断,安装模式和权限 边界见 ADB安装与权限管理。读完后,你应能从一条命令找到 wrapper、 cmd 路由、Binder service、状态 owner 和消费者,并知道结果何时生效、失败后由谁清理。
1. 命令路由
1.1 路由状态
Android 17 的四个入口脚本很短。am、pm 和 wm 分别路由到不同的 Binder service:
源码文件:frameworks/base/cmds/am/am.sh
if [ "$1" != "instrument" ] ; then
cmd activity "$@"
else
export CLASSPATH=/system/framework/am.jar
exec app_process /system/bin com.android.commands.am.Am "$@"
fi源码文件:frameworks/base/cmds/pm/pm.sh
cmd package "$@"源码文件:frameworks/base/cmds/wm/wm.sh
cmd window "$@"svc 对部分稳定命令也直接翻译到 cmd:svc wifi 进入 cmd wifi,svc data 进入 cmd phone,svc bluetooth 进入 cmd bluetooth_manager。仍由 Java 命令类处理的命令则通过 app_process 启动 com.android.commands.svc.Svc。
| 入口 | 第一跳 | 主要 service command | 状态 owner | 典型消费者 |
|---|---|---|---|---|
am | cmd activity,instrument 例外 | ActivityManagerShellCommand | AMS/ATMS 的进程、任务和启动状态 | ActivityTaskManager、应用进程、系统 UI |
pm | cmd package | PackageManagerShellCommand | PackageManagerService/PackageInstallerSession | resolver、安装器、PermissionManagerService |
wm | cmd window | WindowManagerShellCommand | WindowManagerService 的显示与窗口策略 | SurfaceFlinger、输入系统、System UI |
svc | cmd 或 app_process | PowerCommand、UsbCommand 等 | 对应 Binder service 或 HAL 状态 | 电源、USB、NFC、系统服务 |
cmd 只负责按 service name 找到 Binder 对象并调用其 shellCommand();它不是 Activity、Package 或 Window 状态的 owner。这个区分解释了为什么同一条命令的输出可以很快返回,但屏幕、进程或安装状态 稍后才改变。
1.2 instrument
am instrument 需要本地 Java 命令类建立 instrumentation 参数,因此 am.sh 不把它交给 cmd activity,而是设置 CLASSPATH 后启动 com.android.commands.am.Am。该类取得 IActivityManager 和 IPackageManager,再调用 Binder 接口。普通 am start 则直接由 ActivityManagerService.onShellCommand() 创建 ActivityManagerShellCommand。
这不是两个不同的 Activity 管理系统:差异只在参数解析入口。读源码时应先确认命令是否被 wrapper 特殊分流,再进入 service 的 onShellCommand,不要把 am 二进制名当作唯一入口。
2. am 启动请求
2.1 amstart
am start -W -n package/.MainActivity 的普通路径是:
am.sh执行cmd activity start ...。cmd找到activityBinder service,调用ActivityManagerService.onShellCommand()。ActivityManagerShellCommand.onCommand()解析start,构造 Intent 和用户参数。- AMS/ATMS 校验调用者、用户和组件,创建或复用 task,并把启动请求交给启动器。
- 窗口、进程和客户端事务继续异步推进;
-W只让 shell command 等待启动观察结果,不改变 owner。
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
相关函数/类型:onShellCommand
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
(new ActivityManagerShellCommand(this, false)).exec(
this, in, out, err, args, callback, resultReceiver);
}ActivityManagerShellCommand 是一次请求的临时 owner;Activity、Task 和启动状态仍由 ATMS/AMS 持有。启动完成的消费者包括 WindowManager、应用进程和 System UI,因此 shell 返回 0 只能证明 命令请求被接受,不能单独证明首帧已经显示。
2.2 启动成功
| 阶段 | 可能结果 | 应追的 owner/消费者 |
|---|---|---|
| 参数解析 | 组件名、用户或 flag 非法 | ActivityManagerShellCommand,无系统状态改变 |
| 权限与用户检查 | shell 无权启动、用户不存在 | AMS/ATMS 的调用者和 user 检查 |
| resolve/start | 组件不存在、后台启动受限 | PackageManager resolve 与 ATMS 启动策略 |
| 进程/窗口阶段 | 应用进程启动失败或 ANR | ActivityRecord、ProcessRecord、WindowManager |
| 中断 | shell 被杀或 Binder 断开 | 请求回调断开;已创建的 task/进程按 service 生命周期收敛 |
启动请求没有一个由 am 自己执行的“回滚全部状态”步骤。失败后应查询 dumpsys activity,确认 task、ActivityRecord 和进程是否已经创建,再决定使用 am force-stop 或清理测试用户;不要把 shell 退出码当作完整回滚证明。
2.3 AM验证
先运行一个明确组件,再分别观察 shell 结果和服务状态:
adb shell am start -W -n com.android.settings/.Settings
adb shell dumpsys activity activities | sed -n '/ResumedActivity/,+3p'
adb shell am force-stop com.android.settings
adb shell dumpsys activity processes | grep com.android.settings这里的断言范围是:ResumedActivity 能证明 ATMS 已记录 resumed 状态,force-stop 后进程列表应不再 显示该包的运行进程。它不能证明 Settings 的每个窗口都已完成绘制,也不能证明厂商启动动画策略。
3. pm 安装与查询
3.1 pm path
pm.sh 永远执行 cmd package。PackageManagerService.onShellCommand() 再创建 PackageManagerShellCommand,因此 pm path com.example.app 与 pm install file.apk 在同一个 service 入口下分叉:前者读取已提交的 package state,后者创建 Package Installer session。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
(new PackageManagerShellCommand(this, mContext,
mDomainVerificationManager.getShell()))
.exec(this, in, out, err, args, callback, resultReceiver);
}安装的 owner 不是 PackageManagerShellCommand,而是 PackageInstallerSession。shell command 负责 创建 session、写入 APK、调用 commit,并在写入或提交失败时 abandon。提交后的 package state 由 PMS 持有,resolver、安装器和权限服务随后消费它。
| 命令 | 状态变化 | 生效时机 | 只读验证 |
|---|---|---|---|
pm path PKG | 无 | 立即读取 PMS 已提交状态 | 输出 APK/APEX 路径 |
pm install APK | 创建并提交 session | commit 完成后才更新 package state | pm path PKG、dumpsys package PKG |
pm install-abandon ID | 终止未提交 session | abandon 返回后释放 session 资源 | pm install-sessions |
pm clear --user ID PKG | 清理用户数据 | 清理服务完成后生效 | dumpsys package 的 data 状态 |
3.2 失败范围
安装可能在 APK 读取、签名、版本、ABI、存储、用户或 commit 阶段失败。失败 session 的清理由 PackageManagerShellCommand 和 PackageInstallerSession 协作完成,shell 进程消失不会让一个已提交 的安装自动回滚。
adb install -g 是创建安装参数时设置“授予可授予 runtime permission”的 install flag;它不是 任意权限的万能 grant。pm grant PKG PERM --user ID 则进入 runGrantRevokePermission,最后由 PermissionManagerService 按 package、permission 和 user 校验并更新授权状态。签名权限、固定权限、 不存在的用户或不属于该包的权限都会失败。
3.3 PM验证
adb install app-debug.apk
adb shell pm path com.example.app
adb shell dumpsys package com.example.app | sed -n '/Package \\[com.example.app\]/,+20p'
adb shell pm grant --user 0 com.example.app android.permission.POST_NOTIFICATIONS
adb shell pm revoke --user 0 com.example.app android.permission.POST_NOTIFICATIONS成功安装后,pm path 应返回实际安装路径;grant/revoke 的覆盖边界只到 PermissionManager 的用户权限 状态,不证明应用已经重新读取权限或 UI 已更新。没有对应 APK、权限声明或可用用户时,命令失败是预期 边界,而不是 ADB 通道故障。
4. wm 显示配置
4.1 消费者
wm.sh 把参数送到 cmd window,由 WindowManagerService.onShellCommand() 创建 WindowManagerShellCommand。wm size 修改的是 WindowManager 的显示 override 配置;WindowManager 随后把新的 display configuration 传播给 DisplayContent、ActivityTaskManager、输入系统和 SurfaceFlinger。
源码文件:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
new WindowManagerShellCommand(this).exec(
this, in, out, err, args, callback, resultReceiver);
}wm size reset 只删除 override,不是恢复出厂显示硬件;最终尺寸仍受物理 display、overlay、分辨率策略 和产品配置约束。命令返回后立刻读取一次可能看到旧消费者状态,因为配置广播和窗口重布局是异步的。
adb shell wm size
adb shell wm size 1080x1920
adb shell wm size
adb shell wm size reset
adb shell wm size可验证的断言是 WindowManager 的 override 文本发生变化并在 reset 后消失。它不能单凭命令输出证明 SurfaceFlinger 已完成下一帧合成;应结合 dumpsys display 或实际窗口布局观察传播结果。
4.2 失败与恢复
非法尺寸、权限不足、显示不存在或设备策略拒绝时,override 不应被写入。命令执行中 shell 断开时, 已经提交到 WindowManager 的 override 不会因为 shell 退出自动撤销,所以实验结束必须显式执行 wm size reset。这正是“请求生命周期”和“配置生命周期”不同的例子。
5. svc
5.1 命令表边界
Svc.java 通过 lookupCommand() 选择 help、power、usb、nfc 或 system-server 命令。 PowerCommand 直接取得 IPowerManager,调用 setStayOnSetting、reboot、shutdown 或 forceSuspend;UsbCommand 则同时包含同步 Binder 调用和带 operation id 的异步回调。
| 命令 | 入口 | 生效特征 | 清理/恢复 |
|---|---|---|---|
svc power stayon usb | IPowerManager.setStayOnSetting | 设置写入后由电源策略消费 | svc power stayoff |
svc power reboot | IPowerManager.reboot | 设备重启,shell 不会等待完整启动 | 重启后由系统恢复 |
svc usb getFunctions | UsbCommand 查询 | 同步返回当前 USB function | 无状态改变 |
svc usb setFunctions mtp | UsbCommand + USB service | 可能异步完成 | 查询 functions,失败时按服务状态恢复 |
svc nfc enable | NfcAdapter.enable | 由 NFC service/HAL 异步打开 | svc nfc disable |
5.2 电源边界
svc power reboot 和 shutdown 的调用成功只表示 PowerManager 接受了请求;设备随后进入重启或关机 流程,shell 连接可能立即断开。不要把断开当作失败,也不要在同一条脚本里假设设备马上可用。可恢复 的状态设置(例如 stay-on 或 USB function)应在实验结尾显式恢复,而 reboot/shutdown 的恢复只能由 后续启动阶段完成。
6. 命令诊断
6.1 诊断状态
遇到命令返回 0 但现象未变,按以下边界排查:
- wrapper 是否把命令分流到了另一个入口(特别是
am instrument和svc的翻译命令)。 cmd找到的 Binder service 是否属于预期 service name。- shell command 是否只创建了 session、task 或 override,真正消费者是否异步处理。
- 目标 user、权限、feature、HAL 或产品策略是否拒绝了后续阶段。
- 是否需要用相应的只读命令再次读取 owner 状态,而不是重复执行写命令。
6.2 源码搜索路线
rg -n "onShellCommand|new .*ShellCommand" frameworks/base/services/core/java/com/android/server
rg -n "runInstall|runGrantRevokePermission" frameworks/base/services/core/java/com/android/server/pm
rg -n "class WindowManagerShellCommand|runDisplaySize" frameworks/base/services/core/java/com/android/server/wm
rg -n "lookupCommand|class PowerCommand|class UsbCommand" frameworks/base/cmds/svc搜索结果应继续追到状态 owner 和消费者:AMS/ATMS 的 ActivityRecord/Task,PMS 的 package state 和 PackageInstallerSession,WMS 的 DisplayContent,以及电源/USB/NFC service。只停在 shell command 类会把“参数被接受”误认为“系统状态已经改变”。
6.3 命令验证
本文的调用链来自 android-17.0.0_r1。实际命令是否可用,还取决于设备是否启动目标 service、调用者 权限、当前 user、产品 overlay、HAL 能力和系统是否完成 boot。没有设备时可以静态验证 wrapper、 onShellCommand 分支、session/override 的 owner 和测试断言,但不能证明某个厂商产品的显示传播、USB HAL 回调时序、安装签名策略或重启后的实际可用时间。
7. 命令结果
拿任意一条 am、pm、wm 或 svc 命令,写下四行:
- wrapper 或
app_process的第一跳是什么? - 哪个 Binder service 的
onShellCommand接收参数? - 哪个对象拥有被改变的状态,哪个对象消费它?
- 命令返回后,哪些结果同步可见,哪些需要等待或再次查询?失败、中断和实验清理分别在哪里发生?
例如 wm size 的答案应包括 wm.sh → cmd window → WindowManagerShellCommand → WMS override,并指出 dumpsys display 只能帮助观察传播,不能代替 SurfaceFlinger 的最终合成证明。能完整回答这四行,才算 真正从命令名走到了源码和运行结果。
