Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


title: dumpsys 系列命令 chapter: '15.4' section: '15.4' task2b_state: fixed pipeline_stage: ready-to-publish applicable_versions: Android 6.0 (API 23) – Android 17 (API 37) last_verified: '2026-08-13' last_verified_against: AOSP android-17.0.0_r1 confidence: medium sources:

  • type: blog path: source.android.com/docs/core/graphics/surfaceflinger-windowmanager tags:
  • dumpsys
  • meminfo
  • gfxinfo
  • activity
  • window
  • batterystats
  • SurfaceFlinger
  • debugging related_chapters:
  • '4.1'
  • '4.4'
  • '7.2'
  • '14.1'
  • '15.1' status: finalized task6_state: reviewed task9_state: reviewed

dumpsys 系列命令

dumpsys 提供的是各系统服务在某一时刻或一段累计周期内的内部状态,适合快速缩小范围,不等同于完整时间线。调用每个子命令前要确认统计窗口、重置行为和字段口径,再与 trace、日志或复现步骤交叉验证。

为什么需要 dumpsys

dumpsys 读取某个 Binder 服务在采集时刻愿意公开的内部状态。Binder 是 Android 的进程间调用机制,ServiceManager 是系统服务注册表;dumpsys 会找到目标服务并调用它的 dump 接口,不会直接扫描服务进程的内存。

它适合回答“当前焦点在哪个窗口”“进程现在处于哪个 OOM 调整级别”“最近保留了哪些 HWUI 帧”等问题。Perfetto、Winscope 和 bugreport 负责补足时间顺序;一份文本快照无法证明事件先后。

Android 17 的 dumpsys.cpp 会从 ServiceManager 取得服务列表,用 checkService() 找到目标 Binder,再调用 IBinder::dump()。全量模式按服务名排序后逐项执行,单个服务的默认超时为 10 秒。超时只表示 dumpsys 不再等待该输出,不能据此认定服务已经停止内部采集。

下面的命令用于确认设备上有哪些服务,并控制一次 dump 的范围。

# 列出当前注册且可见的 Binder 服务
adb shell dumpsys -l

# 把单个服务的等待上限设为 3 秒
adb shell dumpsys -T 3000 activity activities

# 只查询服务所在进程的 PID
adb shell dumpsys --pid SurfaceFlinger

# 只 dump 注册为 CRITICAL 优先级的服务
adb shell dumpsys --priority CRITICAL

-t 的单位是秒,-T 的单位是毫秒;二者属于 dumpsys 的全局参数,应写在服务名之前。全量模式下,全局 --proto 会筛选注册时声明 DUMP_FLAG_PROTO 的服务,并向这些服务传入 proto 参数。指定单个服务时,要遵循该服务自己的参数顺序,例如 dumpsys activity --proto。proto 只表示 Protocol Buffers 编码,不能把任意文本输出自动转换成字段稳定的 schema(结构定义)。

adb shell 发起的命令通常具备平台 DUMP 权限。UID 是 Linux 用来区分调用身份的数字;user build 是面向用户发布的系统构建;SELinux 再按进程域限制可访问的服务和文件。普通应用 UID、受限 user build、厂商服务的额外权限检查和 SELinux 策略仍可能裁剪或拒绝输出。脚本应保存 build fingerprint(唯一标识这版系统构建的字符串)、命令行和采集时间,字段名称也不能视作 SDK 兼容承诺。

平台实现固定到 android-17.0.0_r1。涉及 /proc 记账的说明固定到 android17-6.18-2026-06_r6/proc 是内核向用户空间暴露进程和系统状态的伪文件系统。HWC(Hardware Composer,硬件合成器接口)和厂商服务扩展要以设备对应分支复核。

dumpsys activity:Activity 栈、进程与 ANR 信息

activity 服务横跨 ActivityTaskManager(负责 Activity 与 Task 生命周期和层级)、进程管理、Service 与退出记录。ANR 是 Application Not Responding,表示应用在规定时间内没有响应系统请求。一次排障应只请求相关子项,避免在庞大的默认输出中丢失现场。

下面的命令分别采集任务栈、进程 OOM 状态和退出历史。

# Task、ActivityRecord 与各显示器上的 resumed 状态
adb shell dumpsys activity activities

# 完整进程记录;oom 子项更适合只看 OOM 调整结果
adb shell dumpsys activity processes
adb shell dumpsys activity oom

# ApplicationExitInfo 历史与最近一次 ANR 快照
adb shell dumpsys activity exit-info com.example.app
adb shell dumpsys activity lastanr

activities 输出按 display、TaskDisplayArea、root task 和 Task 组织。display 是物理屏、虚拟屏等显示目标;TaskDisplayArea 是容纳任务的显示区域;root task 是其下 Task 的根容器。排查生命周期时关注 ActivityRecordstatevisiblefinishing、所属 taskId,并记录目标 display。Resumed:topDisplayFocusedRootTask 描述 ActivityTaskManager 的任务状态;它们不等同于窗口焦点,也不保证 InputDispatcher 正向该 Activity 投递输入。

反复执行该命令可以观察状态是否长期卡住,却不适合作为启动耗时计时器。两次 shell 调用之间已经跨过 Binder 调度、格式化和传输开销;启动阶段的毫秒级时序应交给 Perfetto、event log 或 launch metrics。

读懂 OOM 调整值

OOM adjustment(简称 OOM adj)是系统交给 lmkd 的进程保护分数,不表示进程已经发生 OOM。Android 17 的 psc/Constants.java 定义了几个基准值:FOREGROUND_APP_ADJ=0VISIBLE_APP_ADJ=100PERCEPTIBLE_APP_ADJ=200,cached 区间从 900 开始。数值越大,内存压力下通常越早进入回收候选;负值留给 system、persistent 等高保护级别进程。

processes 详细记录中的字段要分开读:

字段含义
curRaw本轮计算中尚未施加部分修正的 raw adj
setRaw上次写入记录的 raw adj
cur本轮计算后的目标 adj
set已提交给进程/LMKD 路径的 adj
adjTypeadjSourceadjTarget哪个组件关系抬高或降低了进程保护级别

可见进程还可能受 laddering 影响:系统按可见层级等信息把多个进程分配到 100—199 之间的细分值,具体行为也受设备配置控制。发现 set 与界面状态不符时,应连同 adjType、绑定服务、ContentProvider、前台服务和显示器可见性一起核对,不能只用一个数字判定 OOM 计算错误。LMK 归因还要对照 PSI(Pressure Stall Information,内存压力导致任务停顿的内核指标)、lmkd 日志和退出原因。

ANR 与退出历史

Android 11 起的 exit-info 来自 ApplicationExitInfo 历史;每条记录描述一次进程退出,不表示当前进程状态。它可区分 ANR、Java/native crash、low-memory kill、用户请求和初始化失败等原因,并保留多个进程实例。Android 17 的 ActivityManagerService 把该子命令分发给 AppExitInfoTracker

lastanr 是 ActivityTaskManager 保存的最近 ANR 状态,容量和生命周期都有限。exit-info 用于确定退出类型与进程实例,ANR trace、tombstone(native crash 的原生崩溃报告)、bugreport 和 Perfetto 用于解释线程阻塞位置。设备重启、历史裁剪或厂商策略都可能让旧记录消失,线上采集应尽早完成。

dumpsys meminfo:系统和进程内存全景

meminfo 服务由 ActivityManagerService 注册。Android 17 的 MemBinder 会检查 DUMP 与 usage stats(应用使用情况访问)权限,再进入 dumpApplicationMemoryUsage()

下面的命令覆盖全局、单进程和多进程包三个采集范围。

# 全系统汇总
adb shell dumpsys meminfo

# 单个进程;-d 展开 ART/Dalvik 细项,-s 只保留 App Summary
adb shell dumpsys meminfo -d com.example.app
adb shell dumpsys meminfo -s com.example.app

# 按包名收集所有已加载该包的进程
adb shell dumpsys meminfo --package com.example.app

Android 17 支持 -a/-d/-c/-s/-S/-p/--unreachable/--oom/--local/--package/--checkin/--proto/--logstats 等选项,没有 --slab。内核 slab 先看 /proc/meminfoSlabSReclaimableSUnreclaim;有足够权限时再读 /proc/slabinfo

读数口径

指标口径适用问题
RSS当前驻留在物理内存中的页,shared page 会在每个映射进程重复计算进程驻留规模、LMKD 相关现场
PSSshared page 按映射者数量比例分摊跨进程归属和版本内对比
USSprivate clean 与 private dirty 的合计进程退出后较可能直接释放的私有页
Private Dirty进程私有且已修改的页私有匿名内存、COW(Copy-on-Write,写时复制)后的变化
SwapPss换出页按共享关系分摊zRAM(在内存中压缩的交换设备)或其他 swap 参与后的进程归属

PSS 是共享页的归属模型,不是硬件直接计量的数值。Android 17 的 Debug.MemoryInfo.getTotalPss() 在内核提供 SwapPss 时,会把按共享关系分摊的换出页纳入 total;解析脚本不能再把单独的 SwapPss 列重复加到 TOTAL PSS。内核的 smaps/PSS 生成路径可从固定 kernel tag 的 fs/proc/task_mmu.c 核对。

分类行只负责定位方向

  • Dalvik Heap / ART 相关行增长,可能来自存活对象、缓存、尚未执行的 GC 或分配抖动。HPROF、LeakCanary 和 allocation profiling 才能区分对象持有与短命分配。
  • Native Heap 主要对应 allocator 管理的 native allocation。大型独立 mmap(内存映射)、allocator arena(分配器管理的一组内存区)与碎片,会让它和 heapprofd 记录的存活分配字节存在差异。
  • Graphics 依赖 graphics driver 通过 libmemtrack 上报 smaps 未覆盖的归属。GraphicBuffer 是图形缓冲对象,dma-buf 是让驱动和进程共享缓冲区的内核机制;相关内存可能分散在 GraphicsGL、设备映射及其他类别,某一行下降缓慢不能单独证明纹理泄漏。
  • CodeStack.so mmap 等文件映射和线程相关分类要结合进程数、ABI(应用二进制接口)、动态模块与线程数量解释。

完整的对象图、native 分配栈和 dma-buf 归因流程见 §15.3 内存分析工具

采样趋势

workload 指一套可重复的操作负载。下面的主机端循环用于保存同一 workload 下的摘要趋势。

for round in $(seq 1 10); do
  printf 'round=%s host_time=%s\n' "$round" "$(date +%s)"
  adb shell dumpsys meminfo -s com.example.app
  sleep 10
done

每轮 workload、前后台状态、进程 PID、设备温度和等待时间都要固定。稳定增长只构成继续取证的条件:Java 类别转 HPROF/LeakCanary,Native Heap 转 heapprofd,Graphics 转 memtrack、dmabuf_dump 与 SurfaceFlinger。进程重启、GC、trim、缓存上限和 zRAM 变化都会改变曲线形状。

dumpsys gfxinfo:帧渲染统计与 Jank 定位

gfxinfo Binder 由 ActivityManagerService 注册。HWUI 是 Android 的硬件加速 UI 渲染管线,ThreadedRenderer 负责把 View 绘制工作交给 RenderThread。服务找到目标包的运行进程后,经 IApplicationThread.dumpGfxInfo() 进入应用进程,再由 ThreadedRenderer 和各 Window 的 renderer 输出 HWUI 统计。应用卡死时,该跨进程链路也可能超时。

下面的三条命令用于划定一段可重复的 HWUI 测试窗口。

# 清空进程内保存的 HWUI 帧数据
adb shell dumpsys gfxinfo com.example.app reset

# 复现后读取聚合统计
adb shell dumpsys gfxinfo com.example.app

# 输出环形缓冲区中的逐帧列
adb shell dumpsys gfxinfo com.example.app framestats

Android 17 的 ThreadedRenderer.dumpArgsToFlags() 识别 resetframestats。目标包有多个进程或多个硬件加速 Window 时,输出会出现多个分段;采集脚本要保留 pid 和 Window 标题。

聚合统计如何生成

Android 17 同时打印 deadline-aware 与 legacy 统计:前者使用每帧 deadline,后者保留旧版固定刷新间隔模型。当前 JankTracker::finishFrame()GpuCompleted 是否越过调整后的 FrameDeadline 记录 Janky frames;legacy 路径使用旧的固定间隔、swap deadline 与 triple-buffering(三缓冲队列)修正规则。VRR/ARR(可变/自适应刷新率)场景优先读当前 deadline 口径,再用 legacy 行观察兼容指标。

ProfileData.cpp 在 Android 17 输出以下分类:

输出AOSP 中的区间或判定解读边界
Janky framesGpuCompleted >= FrameDeadlineHWUI deadline miss,仍需 trace 定位责任线程
Janky frames (legacy)旧 fixed-interval 模型供历史报表兼容,不宜用于 VRR 精确判定
Number Missed VsyncIntendedVsync → VsyncApp 收到帧回调已晚
Number Slow UI threadVsync → SyncStart 超过当前 frame interval 的 50%UI 阶段偏长,原因可能含输入、动画、traversal、draw record
Number Slow bitmap uploadsSyncStart → IssueDrawCommandsStart 超过 20%这是保留的输出名称,不能只凭名称认定发生了图片解码
Number Slow issue draw commandsIssueDrawCommandsStart → FrameCompleted 超过 75%RenderThread 后半段偏长,不直接区分 CPU、GPU、queue 或 fence wait
Number Frame deadline missed当前 deadline miss 计数Janky frames 的当前口径对应

源码没有名为 Number Slow RenderThread 的固定输出。分位值来自 IntendedVsync → FrameCompleted 的时长直方图;bucket 是直方图中的时间区间。99th percentile 为 50 ms,表示该统计窗口内约 1% 的已记录帧落在 50 ms 或更慢的 bucket,不能换算成“连续丢三帧”。

没有跨应用通用的 5% 合格线。基线应来自相同设备、刷新率、温控状态和 workload,并配合 FrameTimeline 的 jank type、用户可见场景和业务目标判断。

framestats 的列

Android 17 的 FrameInfo.h 包含 IntendedVsyncVsyncInputEventIdHandleInputStartAnimationStartPerformTraversalsStartDrawStartFrameDeadlineFrameStartTimeFrameIntervalWorkloadTargetSyncQueuedSyncStartIssueDrawCommandsStartSwapBuffersFrameCompletedGpuCompleted 等字段。JankTracker 使用固定容量为 120 项的环形缓冲区,写满后会覆盖最早记录,所以这里只保留近期帧。

解析时读取 ---PROFILEDATA--- 后的 header,按列名建索引。不同 Android 版本会增加字段;按固定列号解析会把后续时间戳整体错位。未填充字段还可能使用 0、负值或最大整数哨兵,计算差值前要过滤。

几个常用区间可以帮助缩小范围:

  • Vsync → SyncStart 覆盖 UI 线程消费帧的主要阶段。
  • PerformTraversalsStart → DrawStart 可观察 traversal 中 draw 之前的工作,但其中不只含 measure/layout。
  • SyncQueued → SyncStart 是 RenderThread 排队等待。
  • SyncStart → IssueDrawCommandsStart 覆盖同步与资源上传相关阶段。
  • IssueDrawCommandsStart → FrameCompleted 覆盖 RenderThread 发出绘制命令后的剩余工作。
  • GpuCompleted 是 GPU 完成边界可用时的时间点,和 FrameCompleted 口径不同。

这些列能标出异常区间,方法级归因仍应回到 Perfetto 的 UI Thread、RenderThread、GPU、FrameTimeline、BufferQueue 和 fence 证据。BufferQueue 连接画面生产者与消费者;fence 是表示 GPU 或显示工作何时完成的同步对象;BLAST 用来协调 buffer 与 SurfaceControl transaction。App/HWUI、BLAST、SurfaceFlinger FrontEnd、CompositionEngine 与 HWC 要分层观察,单个 HWUI 行无法覆盖显示后半段。

dumpsys cpuinfo:CPU 占用快速排查

cpuinfo 输出 ActivityManager 后台维护的 ProcessCpuTracker 采样结果。它展示最近两个采样点之间的增量,调用命令的时刻不一定触发一段新的测量窗口,因此“瞬时 CPU”这个叫法会高估它的时间精度。

下面的命令用于取得最近窗口,并在确定目标 PID 后切换到连续线程观察。

# 最近一个 ProcessCpuTracker 采样窗口
adb shell dumpsys cpuinfo

# 连续观察目标进程的线程
APP_PID=$(adb shell pidof com.example.app | tr -d '\r')
adb shell top -H -p "$APP_PID"

Android 17 的 AppProfiler.CpuBinder 直接打印 tracker 保存的 current load/state。ProcessCpuTracker 再用 /proc/stat/proc/<pid>/stat 的增量生成各行。

  • 进程行的 userkernel 分别来自该进程的用户态、内核态 CPU 时间。
  • iowaitirqsoftirq 只在 TOTAL 行传入,不能从某个进程行反推中断归属。
  • minor / major faults 是采样窗口内的进程 fault 增量。
  • 多线程进程能并行占用多个 CPU,进程百分比可能超过 100%。

page fault(缺页异常)表示进程访问的虚拟页尚未准备好:minor fault 通常可直接从内存建立映射,major fault 通常需要等待存储读取。fault 次数本身不等于故障或崩溃。

没有适用于所有后台进程的固定 5% 告警线。同步、媒体、定位、编译和空闲进程的合理基线差异很大。连续异常应由 top 或 Perfetto sched 轨道确认运行时间,再用 Simpleperf 定位函数;TOTAL 中 iowait/irq 异常则转向 block I/O、irq/softirq 与设备驱动证据。

跨设备比较 fault 次数时要记录页大小。Android 15 至 Android 17 可运行在 16 KB page-size 设备上,同样的访问范围可能产生更少的 fault。下面的命令用于保存该实验条件。

adb shell getconf PAGESIZE

该值只说明内核页大小;major fault 仍要结合文件映射、存储等待和回收压力解释。Android 17 kernel 的全局 CPU 统计来源可从 fs/proc/stat.c 复核。

dumpsys window:窗口层级与焦点

窗口问题至少有三个观察者:ActivityTaskManager 管 Activity/Task,WindowManager 管 WindowState 与 display,InputDispatcher 负责选择输入事件的接收窗口。三个焦点字段相同只是一种常见稳定状态。

下面的命令按显示器保存 WMS 与 InputDispatcher 两份现场。

# WindowState 列表、显示器状态与焦点
adb shell dumpsys window windows
adb shell dumpsys window displays

# 只做人工浏览时可缩小字段;正式附件应保留原始输出
adb shell dumpsys window displays \
  | grep -E 'DisplayContent|mCurrentFocus|mFocusedApp'

# 输入分发器当前的 focused application/window
adb shell dumpsys input \
  | grep -E 'FocusedApplication|FocusedWindow'

DisplayContent.mCurrentFocus 指向 WMS 当前聚焦的 WindowStatemFocusedApp 指向该 display 上被选中的 Activity token(系统用来标识 Activity 的 Binder 句柄)。通知栏、IME(输入法窗口)、系统对话框、多窗口切换和 transition(窗口状态过渡)期间,两者可能来自不同组件。多屏设备还要保留 displayId,只 grep 一条全局结果会混淆主屏、副屏与虚拟显示。

“窗口有焦点但触摸无响应”要继续检查 InputDispatcher 的 focused window/application、对应 InputChannel(事件传输通道)、touchable region(可接收触摸的区域)、FLAG_NOT_TOUCHABLE、窗口可见性和上层 overlay。WMS 中的 Z-order(窗口前后层级)与 InputDispatcher 的命中顺序也有不同过滤条件,不能仅凭一个 WindowState 排在前面就断言它会收到触摸。

Android 17 的 dump 入口位于 WindowManagerService,每个显示器的焦点状态位于 DisplayContent。insets 是状态栏、导航栏和 IME 等占用或覆盖的屏幕区域。需要跨帧分析窗口层级、transition、insets 和输入区域时,用 Winscope 录制代替反复 grep 文本快照。

dumpsys batterystats:电池使用与功耗分析

batterystats 记录 UID、组件和系统事件在一段统计窗口内的活动及估算归属。power profile 是设备预置的功耗系数表,power stats 是平台提供的子系统能量或活动统计;两者都可能参与估算。输出不是外接功率计的直接读数。

下面的命令用于建立测试窗口、保存历史并恢复详细 history 开关。

# 会清除当前统计和 history;执行前保存仍需使用的现场
adb shell dumpsys batterystats --reset

# 需要 userspace wakelock 时间线时临时开启
adb shell dumpsys batterystats --enable full-wake-history

# 复现后保存聚合、history 和 bugreport
adb shell dumpsys batterystats --charged > batterystats.txt
adb shell dumpsys batterystats --history > batterystats-history.txt
adb bugreport bugreport.zip

# 采集结束后恢复,避免长期放大 history
adb shell dumpsys batterystats --disable full-wake-history

Android 17 的 BatteryStatsService 明确定义了 --charged--history--reset--enable/--disable full-wake-history--usage 等参数。--reset 会清空当前统计,属于有状态操作;共享测试机上要记录执行者和时间。

一轮可比较的测试应固定:

  1. build、设备、电池温度、亮度、网络类型和帐号同步状态;
  2. 充电状态与测试起止时刻;
  3. 前台/后台步骤、静置时长和重复次数;
  4. 目标 UID、进程重启和系统更新情况;
  5. 同期 Perfetto、bugreport 或 Power Profiler 证据。

Wakelock 与功耗归因

partial wakelock 会阻止设备进入 CPU suspend,但不会强制 CPU 持续忙碌。All partial wake locks 给出 UID/名称维度的持有时长和次数。多个 wakelock 可以重叠,持有时长也不等于 CPU 全程运行时长;还要对齐 Battery Historian 的 wake_lockrunning、JobScheduler、alarm、network、GNSS(卫星定位)和 screen 状态。异常名称负责定位调用方,功耗影响要由 suspend 机会、CPU 工作、radio tail(网络传输结束后无线模块继续处于高功耗态的时间)和测得能量共同确认。

Battery Historian 已不再积极维护,适合查看 bugreport 中的系统级历史。官方当前建议优先考虑 system tracing、Macrobenchmark power metric 或 Power Profiler。ODPM(On Device Power Measurement)把设备功耗按 power rail(硬件子系统供电轨)展示,不能自动归因到单个 App。官方当前列出的 Power Profiler ODPM 范围是 Android 10 及以上的 Pixel 6 和后续 Pixel 设备,具体 rail 仍随机型变化。

dumpsys SurfaceFlinger:Layer 信息与合成状态

SurfaceFlinger 是 Android 的系统合成器,负责把多个 Layer 组合成显示输出。Layer 是应用或系统提交的一层画面;FrontEnd 计算 Layer 的状态和层级;CompositionEngine/HWC 决定并执行合成。它的文本 dump 同时包含 FrontEnd、display、scheduler 和统计模块的信息。先选择视角,再保存原始输出。

下面的命令覆盖 Android 17 排查 Layer 与 present 时间的常用入口。

# 默认快照:可见 FrontEnd 列表以及 display/HWC 等综合状态
adb shell dumpsys SurfaceFlinger

# 全部 FrontEnd snapshot、input list、主 hierarchy 与 offscreen hierarchy
adb shell dumpsys SurfaceFlinger --frontend

# Layer 精确名称和 HWC minidump
adb shell dumpsys SurfaceFlinger --list
adb shell dumpsys SurfaceFlinger --hwclayers

# 单 Layer 的历史 present 三元组
LAYER_NAME='com.example.app/com.example.app.MainActivity#123'
adb shell dumpsys SurfaceFlinger --latency "$LAYER_NAME"
adb shell dumpsys SurfaceFlinger --latency-clear "$LAYER_NAME"

# 更适合时间关联的内部统计入口
adb shell dumpsys SurfaceFlinger --frametimeline
adb shell dumpsys SurfaceFlinger --scheduler

Android 17 的 SurfaceFlinger::doDump() 注册了这些参数。pacesetter display 是 SurfaceFlinger 选作调度基准的显示器,VSync period 是其垂直同步周期。目标不存在时,--latency 仍可能只输出这一行周期;脚本要验证后续三列是否存在。

Layer 列表与合成方式

dumpsys SurfaceFlinger 在 Android 15 至 Android 17 上不能按早期资料理解成“默认展开每个旧 Layer 对象的全部属性”。Android 17 的默认路径调用 dumpVisibleFrontEnd() 生成可见的 Composition listInput list,综合 dump 还会追加 display、HWC 和其他模块状态。snapshot 是 FrontEnd 根据请求状态计算出的当前消费快照;hierarchy 是 Layer 的父子与前后层级。--frontend 调用 dumpFrontEnd(),会输出全部 snapshot、主 hierarchy 与 offscreen hierarchy。

读 Layer 时先分清三个视角:

视角命令 / 输出适合回答的问题
名称列表dumpsys SurfaceFlinger --list当前有哪些 Layer,--latency 应该传哪个 layer name
FrontEnd 快照--frontend 或默认输出中的 Composition list / Input list当前 snapshot 的可见性、层级、几何、buffer 与输入状态
HWC minidump--hwclayers采集时刻 HWC/CompositionEngine 暴露的精简 layer 合成状态

SurfaceControl transaction 是一批原子提交的 Layer 状态变化。RequestedLayerState 保存客户端 transaction 合入后的请求状态,LayerSnapshot 是 FrontEnd 为当前状态计算出的消费快照。显示缺失时检查 Composition list、可见性、bounds、transform、crop、alpha、buffer 和 display/layer stack;输入缺失时切到 Input list 与 WindowManager/InputDispatcher。两个列表用途不同。

HWC minidump 中,CLIENT 通常表示 SurfaceFlinger 通过 GPU/RenderEngine 合成,DEVICE 表示交给 HWC 硬件路径处理。这些选择只代表该次采集附近的状态。overlay 资源、圆角、混合、颜色空间、受保护内容、缩放旋转、带宽和厂商策略都可能改变选择。一次 CLIENT composition 不能证明某个 App layer 长期由 GPU 合成;跨帧结论应使用 Perfetto、Winscope、HWC trace 或稳定复现实验。

Android 15+ 的输出变化

从 Android 15 的 FrontEnd 默认路径到 Android 17,dump 的 Layer 主视角已经转向 snapshot。Android 17 的 Composition list 明确标为 top-to-bottom;Input list 由 forEachInputSnapshot() 生成。旧资料中的 Source CropDisplay Frame 和 composition type 表格仍可用于读旧版本或厂商扩展,不能套用为 Android 17 默认文本格式。

android-17.0.0_r1 的 dumper map 包含 --frontend--list--hwclayers--latency--frametimeline--scheduler 等入口,没有 --all-layer。厂商系统出现额外参数时,以设备输出和厂商源码为准。

Android 17 显示后半段可按 FrontEnd snapshot、CompositionEngine 与 AIDL Composer/HWC 三层观察:文本 dump 负责当前状态,Winscope 负责 transaction、Layer hierarchy、可见性和输入区域的跨帧变化,Perfetto 负责 FrameTimeline、scheduler、BufferQueue、fence 与合成耗时。

FrontEnd 架构补充(源码级)

FrontEnd dump 中的对象来自一条明确的数据转换链:

组件Android 17 源码职责
RequestedLayerStateRequestedLayerState.h保存 transaction 请求状态与 Changes
LayerLifecycleManagerLayerLifecycleManager.h管理 requested state、创建/销毁和 handle 生命周期
TransactionHandlerTransactionHandler.h收集 transaction,执行 readiness 过滤并 flush 可应用项
LayerHierarchyBuilderLayerHierarchy.h建立 z-order hierarchy,并表示 mirror 等共享关系
LayerSnapshotBuilderLayerSnapshotBuilder.h根据 hierarchy、display 与全局状态生成 snapshot

这里的 handle 是外部持有 Layer 的标识;readiness 过滤会暂缓条件未满足的 transaction;flush 会取出本轮已经可以应用的 transaction。mirror 表示一个 Layer 层级复用另一处内容。

LayerHierarchyBuilder 的类定义就在 LayerHierarchy.h,源码树中没有 LayerHierarchyBuilder.h。写源码索引时应使用当前 tag 中存在的文件名。

下面的节选用于说明 updateLayerSnapshots() 的锁边界,省略了 tracing、display mirror、legacy layer 与 feature-flag 分支。

mTransactionHandler.collectTransactions();

mLayerLifecycleManager.addLayers(std::move(update.newLayers));
update.transactions = mTransactionHandler.flushTransactions();
mLayerLifecycleManager.applyTransactions(update.transactions);
mLayerLifecycleManager.onHandlesDestroyed(update.destroyedHandles);
mLayerHierarchyBuilder.update(mLayerLifecycleManager);

State drawingState(mDrawingState);
Mutex::Autolock lock(mStateLock);
applyAndCommitDisplayTransactionStatesLocked(update.transactions);
mLayerSnapshotBuilder.update(args);

mStateLock 是保护 SurfaceFlinger 核心状态的互斥锁。事务收集、requested state 应用和 hierarchy 更新位于该锁之前;display transaction 提交与 LayerSnapshotBuilder.update() 仍在锁内。因此,“FrontEnd 整条 snapshot 路径无锁”与 Android 17 实现不符。完整顺序见 SurfaceFlinger::updateLayerSnapshots()

RequestedLayerState::Changes 在该 tag 中有 22 个 bit。下面的源码形状用于核对 dump/trace 中的变化类型。

enum class Changes : uint32_t {
    Created = 1u << 0,
    Destroyed = 1u << 1,
    Hierarchy = 1u << 2,
    Geometry = 1u << 3,
    Content = 1u << 4,
    Input = 1u << 5,
    Z = 1u << 6,
    Mirror = 1u << 7,
    Parent = 1u << 8,
    RelativeParent = 1u << 9,
    Metadata = 1u << 10,
    Visibility = 1u << 11,
    AffectsChildren = 1u << 12,
    FrameRate = 1u << 13,
    VisibleRegion = 1u << 14,
    Buffer = 1u << 15,
    SidebandStream = 1u << 16,
    Animation = 1u << 17,
    BufferSize = 1u << 18,
    GameMode = 1u << 19,
    BufferUsageFlags = 1u << 20,
    PostProcess = 1u << 21,
};

kMustComposite 包含 ContentBufferPostProcess 等变化,表示该轮变化会推动合成判断。snapshot 是否必须完整遍历 hierarchy 是另一件事:tryFastUpdate() 先 merge 发生变化的 snapshot,只在全局变化超出 Content | Buffer 时退出 fast path。

下面的条件节选用于区分这两个判定。

if ((args.layerLifecycleManager.getGlobalChanges().get() &
     ~(RequestedLayerState::Changes::Content |
       RequestedLayerState::Changes::Buffer).get()) != 0) {
    return false;
}
return true;

仅含 Content/Buffer 且没有 force/display change 时,可以跳过完整 hierarchy traversal。它减少的是 snapshot 重算范围,不能推出本帧不会合成,也不能直接换算为固定性能收益。验证收益要看目标设备的 FastPath trace、FrontEnd 工作量与整帧 deadline。

帧延迟信息

--latency 仍是快速检查单 Layer 历史的入口。Android 17 的 SurfaceFlinger::dumpStats() 先打印 pacesetter VSync period,再按精确 layer name 查找 legacy Layer 并调用 Layer::dumpFrameStats()

从第二行开始,每行是一帧的三个时间戳(均为纳秒):

字段含义
第一列desired_present_time该 layer frame 的期望呈现时间
第二列actual_present_time系统记录的呈现时间
第三列frame_ready_timeframe-stats 路径记录的 ready 边界;不等同于 GPU completion 或 HWC present 调用时间

actual - desired 可标记晚呈现,不能独立归因。晚呈现可能来自 producer、acquire fence、transaction readiness、SurfaceFlinger 调度、client composition、HWC、present fence 或显示模式切换。frame_ready 较晚可把方向推向前半段,仍要用同帧 FrameTimeline 和 fence 证据确认。

第一行来自采集时刻的 pacesetter period。VRR/ARR、多 display、layer frame-rate override 与 frame-rate divisor 场景下,它不代表每一行的独立 deadline。解析器还应过滤 0、负值和未完成哨兵;只有三列都有效时才计算差值。

使用顺序建议如下:

  1. --list 复制精确 layer name,并保存 --latency 原文;
  2. 标记 actual 明显晚于 desired 的候选行;
  3. 在 Perfetto FrameTimeline 找对应 display frame 与 surface frame;
  4. 检查 App/HWUI、BLAST/BufferQueue、acquire fence、SF scheduler、CompositionEngine/HWC 和 present fence;
  5. 动态 hierarchy 或输入问题转 Winscope。

这套顺序按显示通路分层,避免把显示端异常全部压到 App RenderThread。

进阶用法

dumpsys package / alarm / jobscheduler

后台执行问题经常需要把声明、调度状态和功耗历史放在一起。下面的命令用于保存三个服务各自的当前视角。

adb shell dumpsys package com.example.app
adb shell dumpsys alarm
adb shell dumpsys jobscheduler
  • package 可核对安装用户、权限授予、组件、intent filter(组件接受哪些 action、data 和 category)、版本和 package state。Manifest 声明存在不表示组件此刻正在运行。
  • alarm 可核对已注册 alarm、batch(系统合并执行的一组 alarm)、wakeup 类型与目标 UID。一次快照中的 alarm 数量不等于一段时间内的实际唤醒频率。
  • jobscheduler 可核对 pending/running job、约束和调度原因。约束包括充电、网络、空闲等执行前提;执行耗时与历史完整度由该版本输出决定,功耗结论还要对照 batterystats 与 trace。

自定义 Service 的 dump 接口

普通应用 Service 是由 ActivityManager 管理的应用组件,不会自动成为 ServiceManager 中可按名字直接查询的顶层 Binder 服务,因此不能假设存在 dumpsys <service_name>。应用可重写 Service.dump(),由 ActivityManager 的 client dump 路径调用;平台 Binder 服务在有权限注册到 ServiceManager 后,可以直接响应 dumpsys

下面的 Java 示例用于给一个已经运行的应用 Service 暴露有限状态。

public final class SyncService extends Service {
    private final AtomicInteger pendingCount = new AtomicInteger();

    @Override
    protected void dump(
            FileDescriptor fd, PrintWriter writer, String[] args) {
        writer.println("pendingCount=" + pendingCount.get());
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

下面命令中的 -c 表示同时请求 client dump。它必须写在 service 子项之前,让 ActivityManager 进入应用进程调用 Service.dump()

adb shell dumpsys activity -c service \
  com.example.app/.SyncService

Android 17 的 ActiveServices.ServiceDumper.dumpWithClient() 通过 IApplicationThread.dumpService() 把请求送到应用主线程,ActivityThread 再调用 Service 实例。Service 未运行、主线程阻塞或组件过滤不匹配时,客户端内容不会出现。

dump 实现应先复制少量状态,再释放业务锁并格式化输出。不要在回调中等待网络、扫描大目录、打印认证 token、帐号或用户内容,也不要持有核心锁执行长时间 I/O。面向自动化的字段要有版本号、明确单位和稳定键名;平台服务可用 PriorityDumper 按 CRITICAL/HIGH/NORMAL 拆分输出,或用 proto 提供机器读取入口。

取证时常犯的错误

  • 直接执行无参数 dumpsys:它会顺序调用大量服务,每个服务默认可等待 10 秒,输出和扰动都很大。用 -l 找服务,再请求子项。
  • 只保存 grep 结果:display、pid、Window 标题、统计起点和上下文一旦丢失,字段很难解释。原始输出作为附件,grep 只用于现场浏览。
  • 把文本字段当稳定 API:dump 格式会随平台和厂商分支变化。脚本按 header/section 解析,支持 proto 的服务优先保存 proto,并记录 build fingerprint。
  • 用一份快照证明趋势:内存、CPU、alarm、job 和 Layer 都可能瞬时波动。固定 workload,重复采样,再用时间工具确认。
  • gfxinfo 的分类名当根因:Slow bitmap uploads 是历史输出名,Slow issue draw commands 也没有区分 CPU、GPU 与 fence。两者都是 trace 入口。
  • dumpsys meminfo 与 Android Studio Memory Profiler 强行对齐:前者按 OS 页和 memtrack 归组;后者会随 recording 类型呈现 Java/Kotlin allocation、native allocation 或进程内存类别。采样范围、时刻和口径不同,差值本身不构成 bug。
  • 把 batterystats 估算当功率计:估算归属、系统事件和 power rails 各有边界,耗电回归要用同条件 A/B 与可测量能量。

采集复核清单

  • 命令、参数顺序、采集时间和 build fingerprint 是否已保存?
  • 输出对应哪个 PID、user、display、Window 或 Layer?
  • 统计是当前快照、最近采样窗口、环形缓冲,还是 since-reset 累计值?
  • reset、--enable full-wake-history 等有状态操作是否已记录并恢复?
  • PSS、frame deadline、present、wakelock 和 CPU 百分比的单位与口径是否写清?
  • 结论是否有第二类证据:Perfetto、Winscope、HPROF、heapprofd、bugreport、tombstone 或功率测量?
  • 厂商字段是否和 AOSP 字段分开标注?

参考资料