Skip to content

系统控制命令

追踪 am、pm、wm、svc 的命令路由、状态所有者、异步生效和失败清理,并用可执行命令验证结果。

基于android-17.0.0_r1
AndroidADBActivityManagerPackageManagerWindowManagerBinder

系统控制命令 ​

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

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

sh
cmd package "$@"

源码文件:frameworks/base/cmds/wm/wm.sh

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典型消费者
amcmd activity,instrument 例外ActivityManagerShellCommandAMS/ATMS 的进程、任务和启动状态ActivityTaskManager、应用进程、系统 UI
pmcmd packagePackageManagerShellCommandPackageManagerService/PackageInstallerSessionresolver、安装器、PermissionManagerService
wmcmd windowWindowManagerShellCommandWindowManagerService 的显示与窗口策略SurfaceFlinger、输入系统、System UI
svccmd 或 app_processPowerCommand、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 的普通路径是:

  1. am.sh 执行 cmd activity start ...。
  2. cmd 找到 activity Binder service,调用 ActivityManagerService.onShellCommand()。
  3. ActivityManagerShellCommand.onCommand() 解析 start,构造 Intent 和用户参数。
  4. AMS/ATMS 校验调用者、用户和组件,创建或复用 task,并把启动请求交给启动器。
  5. 窗口、进程和客户端事务继续异步推进;-W 只让 shell command 等待启动观察结果,不改变 owner。

源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

相关函数/类型:onShellCommand

java
// 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 启动策略
进程/窗口阶段应用进程启动失败或 ANRActivityRecord、ProcessRecord、WindowManager
中断shell 被杀或 Binder 断开请求回调断开;已创建的 task/进程按 service 生命周期收敛

启动请求没有一个由 am 自己执行的“回滚全部状态”步骤。失败后应查询 dumpsys activity,确认 task、ActivityRecord 和进程是否已经创建,再决定使用 am force-stop 或清理测试用户;不要把 shell 退出码当作完整回滚证明。

2.3 AM验证 ​

先运行一个明确组件,再分别观察 shell 结果和服务状态:

bash
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

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创建并提交 sessioncommit 完成后才更新 package statepm path PKG、dumpsys package PKG
pm install-abandon ID终止未提交 sessionabandon 返回后释放 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验证 ​

bash
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

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、分辨率策略 和产品配置约束。命令返回后立刻读取一次可能看到旧消费者状态,因为配置广播和窗口重布局是异步的。

bash
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 usbIPowerManager.setStayOnSetting设置写入后由电源策略消费svc power stayoff
svc power rebootIPowerManager.reboot设备重启,shell 不会等待完整启动重启后由系统恢复
svc usb getFunctionsUsbCommand 查询同步返回当前 USB function无状态改变
svc usb setFunctions mtpUsbCommand + USB service可能异步完成查询 functions,失败时按服务状态恢复
svc nfc enableNfcAdapter.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 但现象未变,按以下边界排查:

  1. wrapper 是否把命令分流到了另一个入口(特别是 am instrument 和 svc 的翻译命令)。
  2. cmd 找到的 Binder service 是否属于预期 service name。
  3. shell command 是否只创建了 session、task 或 override,真正消费者是否异步处理。
  4. 目标 user、权限、feature、HAL 或产品策略是否拒绝了后续阶段。
  5. 是否需要用相应的只读命令再次读取 owner 状态,而不是重复执行写命令。

6.2 源码搜索路线 ​

bash
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 命令,写下四行:

  1. wrapper 或 app_process 的第一跳是什么?
  2. 哪个 Binder service 的 onShellCommand 接收参数?
  3. 哪个对象拥有被改变的状态,哪个对象消费它?
  4. 命令返回后,哪些结果同步可见,哪些需要等待或再次查询?失败、中断和实验清理分别在哪里发生?

例如 wm size 的答案应包括 wm.sh → cmd window → WindowManagerShellCommand → WMS override,并指出 dumpsys display 只能帮助观察传播,不能代替 SurfaceFlinger 的最终合成证明。能完整回答这四行,才算 真正从命令名走到了源码和运行结果。