dumpsys诊断
很多资料把 dumpsys 写成“查看系统信息的命令集合”,然后列出几十个服务名。这种记法能让人复制命令,却不能解释三个更重要的问题:服务名从哪里来、一次 dump 请求由谁执行、输出为空或超时时应该检查哪一层。
本文从 frameworks/native/cmds/dumpsys 的真实代码出发,建立一条可追踪的主线:命令行进入 Dumpsys::main,ServiceManager 提供服务列表和 Binder 对象,Dumpsys 为每个服务创建 pipe 和读取线程,再通过 Binder dump() 把服务自己的诊断输出复制到标准输出。本文还把常用服务按诊断问题组织起来,但不把任何服务的输出字段当作稳定 API。前置的传输边界见 ADB架构,跨服务工件与时间窗关联见 bugreport分析。
本文默认读者已经读过 ADB架构 和 ADB shell机制,知道 host、adbd、Binder service 和 shell 命令之间的边界;bugreport分析 可以帮助理解为什么 dumpsys 常作为更大诊断工件的一部分。读完后,你应能从一个现象选择服务,解释一次 dumpsys service 的调用链,并区分“服务不存在”“服务没有输出”“服务 dump 超时”和“服务自身拒绝请求”。
1. dumpsys
1.1 main.cpp
Android 17 的 native 入口在 frameworks/native/cmds/dumpsys/main.cpp。入口只做两件事:取得默认 IServiceManager,再把 argc/argv 交给 Dumpsys::main。服务的名字、Binder handle 和 dump priority 不由 dumpsys 硬编码维护,而由 ServiceManager 及服务注册者提供。
源码文件:frameworks/native/cmds/dumpsys/main.cpp
相关函数/类型:main
int main(int argc, char* const argv[]) {
signal(SIGPIPE, SIG_IGN);
sp<IServiceManager> sm = defaultServiceManager();
Dumpsys dumpsys(sm.get());
return dumpsys.main(argc, argv);
}Dumpsys 的构造函数只保存一个 IServiceManager*。这说明它不是所有系统状态的 owner;它拥有的只是本次命令的临时编排状态:服务列表、参数、pipe、读取线程和超时计时器。每个系统服务仍然拥有自己的状态和 dump() 实现。
图中最容易混淆的是 Dumpsys 和目标服务。Dumpsys 负责“找谁、以什么模式找、如何读、何时停止”;目标服务负责“输出哪些字段、是否允许调用、如何解释参数”。因此,dumpsys activity 的字段变化应回到 ActivityManagerService 的 dump 实现,而不是继续阅读 cmds/dumpsys。
1.2 IServiceManager
Dumpsys::listServices() 调用 sm_->listServices(priorityFlags),再在 native 侧排序。IServiceManager 的接口注释把这个调用定义为“返回所有现有服务的列表”;Android 17 的统一 backend 最终把 AIDL listServices(int dumpPriority) 转发给实际的 servicemanager,并把服务名转换为 Vector<String16>。
这带来两个边界:
dumpsys -l展示的是当前 ServiceManager 能列出的服务,不是某个固定版本的全量目录。- 服务是否存在、是否已经启动、是否声明为 lazy service,是注册和启动策略的结果;
dumpsys只能按选定的行为查询它。
ServiceManager.addService() 的调用方决定服务名、Binder 对象和 dump priority。Java 侧的 android.os.ServiceManager.addService() 最终进入同一个 Binder service manager;native 服务也可以直接注册。因此服务列表的变化应追查注册者、init 启动项和产品配置,而不是修改 dumpsys 的列表逻辑。
版本边界
服务名和服务输出由 Android release、产品配置、厂商服务和运行状态共同决定。本文给出的常用服务名是诊断入口,不是 Android 17 以外版本的完整保证。
2. 服务列表
2.1 Dumpsys::main
Dumpsys::main() 的默认值很有解释力:超时是 10000 ms,优先级过滤是 DUMP_FLAG_PRIORITY_ALL,dump 类型为空时补成 TYPE_DUMP。也就是说,裸运行 dumpsys 会尝试 dump 所有当前可见服务;dumpsys service 则只选择一个服务。
帮助文本在 dumpsys.cpp::usage() 中直接定义,Android 17 支持:
| 形式 | 作用 | 重要实现点 |
|---|---|---|
dumpsys | dump 所有服务 | 先列出服务,再逐个 dump |
dumpsys -l | 只列服务 | 调用 listServices() 后立即返回 |
dumpsys SERVICE [ARGS] | 指定一个服务并传参数 | 第一个位置参数是服务名,其余进入 Binder dump() |
--skip a,b | dump 除指定服务外的全部服务 | 位置参数被解释为跳过列表 |
--priority CRITICAL|HIGH|NORMAL | 只选择对应 dump priority | 同时向服务传 --dump-priority |
--proto | 只选支持 proto dump 的服务 | 服务列表取两个 priority 列表的交集,并向服务传 --proto |
-t SEC | 秒级超时 | 解析后乘以 1000 |
-T MS | 毫秒级超时 | 直接使用毫秒值 |
--dump | 请求服务普通 dump | 没有其它类型时也是默认值 |
--pid | 输出服务宿主 PID | 不调用服务自己的 dump() |
--thread | 输出 Binder 线程使用量 | 通过 Binder debug 信息读取 |
--clients | 输出客户端 PID | 通过 Binder handle 查询 |
--stability | 输出 Binder stability | 读取 Binder stability 标记 |
-w | 等待服务启动 | 只能与指定服务一起使用,可能无限等待 |
getopt_long() 使用 +t:T:lw,长选项没有短别名。解析完成后,位置参数的第一个被放入 services,后续参数放入 args。如果服务参数中出现旧式 --proto,代码也会把 asProto 置为真,以保持向后兼容。
源码文件:frameworks/native/cmds/dumpsys/dumpsys.cpp
相关函数/类型:Dumpsys::main
if (dumpTypeFlags == 0) {
dumpTypeFlags = TYPE_DUMP;
}
for (int i = optind; i < argc; i++) {
if (skipServices) {
skippedServices.add(String16(argv[i]));
} else if (i == optind) {
services.add(String16(argv[i]));
} else {
args.add(String16(argv[i]));
}
}2.2 互斥选项
解析后有三类非法组合:--skip 没有服务名、-w 没有指定服务、-l 同时带了服务或 skip 列表。代码调用 usage() 并返回 -1,不会尝试猜测用户意图。
例如,下面两条命令并不等价:
adb shell dumpsys -l
adb shell dumpsys --skip activity window第一条只是列目录;第二条先列出当前服务,再 dump 除 activity 和 window 外的其它服务。要只检查一个懒加载服务,应使用 -w 和服务名,而不是把它和全量 dump 混合。
2.3 listServices
--priority 在调用 ServiceManager 时就参与列表选择。--proto 会调用两次 listServices():一次取指定 priority 的服务,一次取 DUMP_FLAG_PROTO 服务,然后按排序后的名字做交集。它不是把普通文本转换成 proto,而是只请求明确声明支持 proto dump 的服务。
筛选完成后,setServiceArgs() 会把额外参数插入传给服务:
源码文件:frameworks/native/cmds/dumpsys/dumpsys.cpp
if (asProto) {
args.insertAt(String16("--proto"), 0);
}
if (priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_ALL ||
priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_NORMAL ||
priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT) {
args.insertAt(String16("-a"), 0);
}
if (priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL ||
priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_HIGH ||
priorityFlags == IServiceManager::DUMP_FLAG_PRIORITY_NORMAL) {
args.insertAt(String16("--dump-priority"), 0);
args.insertAt(ConvertBitmaskToPriorityType(priorityFlags), 1);
}因此 --priority NORMAL 既改变 ServiceManager 的列表,也让服务收到 --dump-priority NORMAL -a。-a 的含义由服务解释,通常表示包含更完整的内部状态;它不是 dumpsys 自己展开的通用字段开关。
3. 服务dump
3.1 checkService
全量 dump 和普通指定服务使用 ServiceBehavior::DUMP_IF_STARTED,对应 sm_->checkService(serviceName)。服务没有运行时,checkService 返回空,dumpsys 打印 Can't find service: <name>,继续处理其它服务。
只有显式使用 -w 时才使用 sm_->waitForService(serviceName)。该路径允许 servicemanager 按 lazy service 机制启动服务,但 dumpsys.h 明确提示配置错误时可能无限等待。这个差别决定了诊断时是否允许等待启动:检查现有状态用默认行为,确认一个声明的懒加载服务能否启动才使用 -w。
3.2 pipe/线程
startDumpThread() 创建匿名 pipe。读端保存到 redirectFd_,写端移动进新线程。线程先按 dumpTypeFlags 写 PID、stability、thread、clients 信息,最后在需要时调用 service->dump(remote_end.get(), args)。
服务的 Binder dump() 可能阻塞,不能直接在主线程调用;主线程必须同时负责 poll、超时和把字节写到最终 fd。BpBinder::dump() 把 fd 和参数放入 Binder transaction,远端 BBinder::onTransact() 再调用具体对象的 dump()。
3.3 dump
这些选项都由 dumpsys 统一调度,但数据来源不同:
| 选项 | 数据来源 | 是否调用服务自己的 dump() | 输出适合回答什么问题 |
|---|---|---|---|
默认/--dump | 目标服务对象 | 是 | 服务内部状态、历史、配置和统计 |
--pid | IBinder::getDebugPid() | 否 | Binder 对象对应的宿主进程 |
--thread | getBinderPidInfo() | 否 | Binder 线程池使用量 |
--clients | Binder handle 与 getBinderClientPids() | 否 | 哪些 PID 持有客户端引用 |
--stability | internal::Stability::debugToString() | 否 | Binder stability 标记 |
多个类型可以组合,例如 dumpsys --pid --stability activity。新线程会按位检查标志并依次写入结果;--dump 仍会作为最后一段普通服务输出。--pid 和 --clients 依赖 Binder debug 信息,返回错误时 reportDumpError() 只打印上下文和 statusToString(),不会伪造一个 PID 或客户端列表。
3.4 读取循环
writeDump() 用 steady_clock 计算 deadline,每次 poll() 都重新计算剩余毫秒。读取按最多 4096 字节进行,再用 WriteFully() 写到 stdout。pipe EOF 表示服务线程关闭写端,正常结束;poll 超时返回 TIMED_OUT,普通文本模式会追加:
*** SERVICE 'activity' DUMP TIMEOUT (10000ms) EXPIRED ***多服务 dump 会在每个服务前写 DUMP OF SERVICE <name>:,结束时写入耗时和本地时间。proto 模式不追加文本 timeout,因为额外文本会污染机器可解析输出。
源码文件:frameworks/native/cmds/dumpsys/dumpsys.cpp
if (rc == 0 || time_left_ms() == 0) {
status = TIMED_OUT;
break;
}
...
if ((status == TIMED_OUT) && (!asProto)) {
WriteStringToFd(timeout_message, fd);
}超时后 stopDumpThread(false) 选择 detach(),然后关闭主线程持有的 pipe 读端。这样主命令不会继续等待卡住的服务线程;服务线程最终因远端 dump 返回、写端错误或进程结束而结束。超时不是“服务没有任何问题”的证明,而是这次读取没有在 deadline 内完成。
4. 命令用法
4.1 服务目录
adb shell dumpsys -l-l 不调用服务的 dump(),只输出 ServiceManager 当前能检查到的服务名。服务名排序由 Dumpsys::listServices() 完成,所以输出顺序适合阅读,但不应当被脚本当成稳定编号。
下面的命令分别对应不同问题:
| 现象 | 首选命令 | owner/消费者 | 交叉证据 |
|---|---|---|---|
| Activity 启动、task、进程状态 | adb shell dumpsys activity | ActivityManagerService | adb logcat 中 ActivityTaskManager/ActivityManager 标签 |
| 窗口、焦点、旋转、Insets | adb shell dumpsys window | WindowManagerService | dumpsys input、SurfaceFlinger transaction 日志 |
| 包、安装、uid、组件 | adb shell dumpsys package <package> | PackageManagerService | pm path、安装命令输出、logcat |
| 内存压力和进程摘要 | adb shell dumpsys meminfo | Activity/内存统计服务 | /proc、adb shell procrank(产品可用性取决于构建) |
| 图形帧、渲染统计 | adb shell dumpsys gfxinfo <package> | GraphicsStats/服务端统计 | SurfaceFlinger、Perfetto |
| 电源、唤醒锁、Doze | adb shell dumpsys power | PowerManagerService | kernel wakeup、logcat |
| 电池历史 | adb shell dumpsys batterystats | BatteryStatsService | 电量、电源 HAL、时间窗 |
| 网络连接和网络评分 | adb shell dumpsys connectivity | ConnectivityService | ip、dumpsys netstats、网络 logcat |
| 合成、显示设备和 Layer | adb shell dumpsys SurfaceFlinger | SurfaceFlinger | dumpsys window、HWC/DRM 日志 |
| 输入设备、焦点和事件 | adb shell dumpsys input | InputManagerService | window focus、evdev/触摸日志 |
服务名必须先以 dumpsys -l 和目标构建实际输出为准;例如某些产品把额外服务注册到 servicemanager,某些服务只在特定 feature 或进程启动后出现。
4.2 服务参数协议
adb shell dumpsys activity top
adb shell dumpsys package com.example.app
adb shell dumpsys gfxinfo com.example.app framestatsdumpsys 只把服务名之后的参数保存到 Vector<String16> args 并传给服务。top、包名和 framestats 的含义分别由 Activity、Package 和 GraphicsStats 的 dump 实现定义。因此要解释参数,应进入目标服务的 dump() 或 ShellCommand 解析,而不能从 dumpsys.cpp 推导字段含义。
-a 是一个例外:当全量、NORMAL 或 DEFAULT priority dump 时,setServiceArgs() 会自动把 -a 放到服务参数最前面。测试 PassAllFlagsToServices 和 PassAllFlagsToNormalServices 通过 mock service 精确断言了这一点。服务是否真正支持 -a,仍由服务实现决定。
4.3 --skip
adb shell dumpsys --skip SurfaceFlinger batterystats该命令会 dump 全部服务,但跳过 SurfaceFlinger 和 batterystats。Dumpsys::main() 在打印当前服务列表时会标记 (skipped),随后在 dump 循环中直接 continue。它不会先执行再丢弃输出,因此能减少慢服务对总时间的影响。
如果只想看少数服务,不要用很长的 skip 列表;直接指定服务名更容易复现,也更容易判断缺失是服务未注册还是被跳过。
4.4 超时单位
adb shell dumpsys -t 30 activity
adb shell dumpsys -T 500 window-t 30 在解析时变成 30000 ms;-T 500 保持 500 ms。数字必须是正整数,尾随字符或零都会返回错误。对于一个偶发慢服务,先用 -T 做短超时观察,再用 -t 给出合理窗口;不要无限增大超时来掩盖服务 dump 自身的锁等待或死循环。
5. 失败路径定位
5.1 服务不存在
startDumpThread() 在 checkService() 或 waitForService() 得到空 Binder 时输出:
Can't find service: example可能原因包括:名字拼写错误、服务尚未启动、服务按产品 feature 未注册、当前权限看不到服务,或 -w 没有被使用。诊断顺序应是:
adb shell dumpsys -l | grep -w example确认目录中是否有名字;- 检查服务启动和注册 owner,而不是先检查 dump 字段;
- 对声明为 lazy 的服务尝试
dumpsys -w example,并设置外部命令超时; - 若仍失败,查看 init、servicemanager 和目标服务日志。
5.2 空正文
服务可能返回 OK 但没有写任何字节:服务的 dump 实现选择了空输出,或者参数让它没有可打印内容。Dumpsys 只统计 bytesWritten,不会把空输出解释为服务不存在。可用不带服务参数的命令与服务专用参数做对照,并查看服务实现是否需要 -a、包名、用户 ID 或 proto 参数。
5.3 timeout 分层
| 层 | 证据 | 结论 |
|---|---|---|
| 查询层 | Can't find service | 没有拿到可用 Binder,尚未进入 dump |
| 读取层 | DUMP TIMEOUT | 服务线程没有在 deadline 内完成写入,可能仍在运行 |
| 服务层 | Error with service ... while dumping | Binder dump 调用返回非 OK,具体错误由服务/Binder 提供 |
超时后主线程会 detach dump 线程并关闭读端;下一条命令是否成功取决于目标服务是否从阻塞点恢复。若每次都在同一服务超时,应转向该服务的锁、I/O、历史数据规模和权限路径,而不是只修改 dumpsys 参数。
5.4 proto输出边界
--proto 只选择支持 proto 的服务,并抑制 writeDump() 的文本 timeout 消息。服务自身若把日志或错误写到同一 fd,仍可能破坏机器解析;因此应先确认该服务的 proto dump 契约,并把 stderr、返回码和输出文件分开保存。不要因为命令带了 --proto 就声称所有 Android 服务都有统一 proto schema。
5.5 shell权限
adb shell 先由 adbd 建立 shell 命令,再启动设备上的 dumpsys 进程。设备端进程能看到哪些服务,受 shell UID、SELinux 域、Binder service 的访问检查和产品策略共同影响。root shell 可以观察到更多内容,但 root 并不会把服务输出字段变成稳定接口;它只改变了权限边界。
保存输出时应让设备命令和 host 重定向的边界清楚:
adb shell dumpsys activity > activity.host.txt
adb shell 'dumpsys activity > /data/local/tmp/activity.device.txt'
adb pull /data/local/tmp/activity.device.txt .第一条由 host shell 创建文件,dumpsys 的 stdout 经过 ADB 返回;第二条的重定向发生在设备 shell 内,文件权限、剩余空间和路径都由设备决定。两条命令看到的文本可能相同,但失败位置不同:第一条常见的是 host 目录不可写,第二条常见的是设备路径权限或空间不足。诊断记录应把命令、执行用户和保存位置一起记下。
对于大输出,优先指定目标服务、使用 --skip 排除已知慢服务,或先把输出保存成文件再分析。不要用 head、终端滚屏或复制粘贴判断服务是否完整;截断发生在消费者侧时,dumpsys 本身并不知道你的终端只读了部分字节。
5.6 Binder查询
当问题是“服务进程是否还活着”而不是“服务内部状态是什么”时,可以先运行:
adb shell dumpsys --pid activity
adb shell dumpsys --thread activity
adb shell dumpsys --clients activity
adb shell dumpsys --stability activity这四条路径绕开服务自定义文本,分别从 Binder 对象取得宿主 PID、线程池使用量、客户端 PID 和 stability 信息。它们适合回答连接关系和进程归属问题,例如窗口服务 dump 卡住时,先确认 Binder 宿主是否存在、线程是否耗尽,再决定是否继续增加普通 dump 的 timeout。
组合选项会在同一个 dump thread 中按固定顺序写入结果;--pid --stability 的输出不是两个独立的 service 查询,也不会自动产生服务状态正文。若目标是查调用方,--clients 只能显示 Binder debug 能识别的 PID,不能替代应用层注册表、死亡通知或权限审计。拿到 PID 后还应结合 ps、/proc/<pid>/status 和 logcat 判断进程身份与生命周期。这四种查询不会替代普通 dump;它们是定位 Binder 拥塞和进程归属的窄入口。保留原始命令和输出时间,便于复核。
6. 参数测试
Android 17 的 frameworks/native/cmds/dumpsys/tests/dumpsys_test.cpp 是理解行为边界的最好入口之一。测试使用 BinderMock 模拟 ServiceManager 返回的 Binder 服务,分别控制服务是否运行、dump 参数、输出内容和是否挂起。
6.1 参数传播验证
PassAllFlagsToServices 注册 Locksmith 和 Valet,期望两者分别收到 {"-a"},然后调用 CallMain({"-T", "500"})。断言证明无服务名时会列出服务,并向普通全量 dump 的每个服务传播 -a。
PassPriorityFlagsToCriticalServices、PassPriorityFlagsToHighServices 和 PassAllFlagsToNormalServices 分别断言服务收到 --dump-priority CRITICAL/HIGH/NORMAL;NORMAL 额外收到 -a。这直接验证了 setServiceArgs() 的条件,而不是凭帮助文本猜测。
DumpWithProto 和 DumpWithPriorityHighAndProto 使用 mock 的 proto priority 列表,断言只有交集中的服务被 dump,并检查服务收到 --proto。它证明 --proto 是列表筛选和参数传播的组合行为,不证明真实服务的 proto 字段稳定。
6.2 Timeout验证
DumpRunningServiceTimeoutInSec 让 mock 服务挂起两秒,调用 CallMain({"-t", "1", "Valet"}),断言输出包含 1000ms。DumpRunningServiceTimeoutInMs 使用 {"-T", "500", "Valet"},断言输出包含 500ms。两者共同证明秒和毫秒在入口处确实走不同转换,但不证明所有服务都能在相同时间内完成。
6.3 Dump状态
DumpWithSkip 设置运行中的 running1、running4 和被跳过的 skipped3、skipped5,另有未运行的 stopped2,调用 --skip 后断言只执行未跳过且运行中的服务。ListAllServicesWithMultipleOptions、ListServiceWithMultipleOptions 则验证 --pid --stability 可以组合,输出同时包含 PID 和 stability。
WriteDumpWithoutThreadStart 直接调用 writeDump() 而未先创建 pipe,断言返回 INVALID_OPERATION。这证明 redirectFd_ 是读取阶段的前置状态,不是一个永远有效的全局 fd。
测试注册在 frameworks/native/cmds/dumpsys/TEST_MAPPING 的 dumpsys_test 中;测试覆盖的是 native 编排器和 mock Binder 行为,不覆盖每个 framework service 的权限、字段、厂商扩展或真实设备上的启动时序。
可以把这些测试整理成一张边界表,避免把“测试通过”泛化成“所有服务都可靠”:
| 结论 | arrange | action | assert | 未证明的范围 |
|---|---|---|---|---|
| 服务列表按目录返回 | mock Locksmith、Valet | -l | 输出服务名且不调用 dump | 真实设备是否注册这些名字 |
| priority 参数传播 | mock 服务并指定 priority | --priority HIGH | 收到 --dump-priority HIGH | 服务如何解释该参数 |
| proto 交集 | mock 普通和 proto 列表 | --proto | 只 dump 交集服务并收到 --proto | proto payload schema |
| 秒/毫秒 timeout | mock dump 挂起 | -t 1 或 -T 500 | 输出对应毫秒数 | 真实服务的阻塞原因 |
| skip | 运行、停止和跳过服务混合 | --skip | 跳过服务不被调用 | 服务注册变化 |
| debug 模式组合 | mock Binder debug 信息 | --pid --stability | 同时有 PID 和 stability | 产品 Binder debug 权限 |
| pipe 前置状态 | 不启动 dump thread | 直接 writeDump() | 返回 INVALID_OPERATION | 线程退出和设备断连 |
这张表的作用是划清测试范围:native 测试可以确认参数和编排控制流,但服务字段、权限和产品启动策略仍需对照对应服务的测试与设备输出。
6.4 Dump验证
DumpMultipleServices 用三个 mock 服务覆盖了一个运行中服务、一个未运行服务和另一个运行中服务,调用 CallMain({})。测试断言运行中的服务输出被写入,未运行服务不会导致整个命令失败。这正是实际执行裸 dumpsys 时需要牢记的行为:它是“尽可能收集当前可用服务”,不是事务式操作;单个服务缺失不会回滚已经写出的其它 section。
ListRunningServices 进一步让一个服务的 checkService() 返回 false,然后调用 -l,断言列表只打印真正拿到 Binder 的服务。因而 dumpsys -l 的“当前运行”语义来自查询时的 Binder 可用性,不等同于静态注册表,也不等同于 VINTF 声明列表。
这也解释了为什么采集脚本需要保存 stderr:stdout 可能已经包含多个成功服务,而 stderr 同时记录某个服务的查询或 dump 错误。把两个流混成一份文本,会丢失“哪些 section 成功、哪个服务失败”的阶段信息。
7. 输出检查
7.1 Dump状态
单条 dumpsys 是一次即时查询,服务输出在不同时间点采样;adb bugreport 则由 dumpstate 编排多个 section,并可能保存为 zip。遇到偶发问题时,可以先用目标服务快速确认现状,再生成 bugreport 保存跨服务证据。
7.2 输出稳定性
服务的文本 dump 通常面向工程师和 bugreport 阅读者:字段可能增删、缩进可能变化、厂商可以追加 section,权限也可能隐藏部分内容。脚本若必须自动化,应优先使用明确的 proto、statsd、系统 API 或测试专用接口;若只能解析文本,应固定 Android release 和产品版本,并为缺字段、权限拒绝和空输出编写测试。
7.3 只读诊断流程
adb shell getprop ro.build.version.incremental
adb shell dumpsys -l > services.txt
adb shell dumpsys activity > activity.txt
adb shell dumpsys window > window.txt
adb logcat -d -b main -b system -v threadtime > logcat.txt
sha256sum services.txt activity.txt window.txt logcat.txt > artifacts.sha256先保存版本和服务目录,再保存目标服务与日志,最后计算 hash。这样后续可以说明输出来自哪个版本、哪个时间窗,而不是只粘贴一段脱离上下文的结果。若某个命令超时,同时保留错误文本和实际 timeout 参数。
8. 源码入口
当你需要深入某个服务时,可以按下面顺序走源码:
- 从
frameworks/native/cmds/dumpsys/dumpsys.cpp::Dumpsys::main确认命令选项最终传了什么参数; - 从
frameworks/native/include/binder/IServiceManager.h和frameworks/native/libs/binder/IServiceManager.cpp确认服务列表、优先级和 Binder 查询的接口边界; - 从
frameworks/native/libs/binder/BpBinder.cpp::dump进入 Binder transaction; - 在目标服务仓库搜索
dump(int fd, const Vector<String16>& args)、dumpLocked或对应的ShellCommand,确认 owner、锁和参数解析; - 回到
dumpsys_test.cpp,检查该行为是否有 mock、端到端或服务测试反向约束; - 用
logcat、bugreport 或 Perfetto 记录服务输出与事件时间的关系。
这条路径能避免一个常见误区:看到 dumpsys package 输出了某个字段,就把字段归因于 ADB。正确的归属是 ADB 负责 transport,dumpsys 负责 Binder dump 编排,PackageManagerService 负责字段和状态,bugreport/dumpstate 负责在更大工件中收集它。
推荐的命令选择
先用 dumpsys -l 确认服务名,再用单服务命令缩小范围;只有需要跨服务快照时才运行裸 dumpsys 或 adb bugreport。遇到 timeout,优先保留现场和时间窗,不要用无限增大的 timeout 掩盖服务内部阻塞。
