1. 为什么你需要一个趁手的性能监测工具做Android开发或者测试的朋友肯定都遇到过这样的场景应用在测试机上跑得好好的一到用户手里就卡顿、闪退、耗电快。用户反馈说“手机发烫”、“用一会儿就卡”但你拿着测试机左看右看就是复现不出来。这时候光靠肉眼和感觉是远远不够的你需要的是数据是能真实反映应用在运行时CPU、内存、电量消耗的量化指标。我以前也吃过这个亏。一个地图应用在内部高配测试机上流畅无比结果上线后大量中低端机用户反馈启动慢、导航过程中卡顿。我们团队折腾了好久最后才发现问题出在某个后台服务的内存泄漏上它在低内存设备上会更快地触发OOM内存溢出。而定位这个问题的第一步就是学会使用Android Debug Bridge也就是我们常说的adb去抓取实时的性能数据。很多人觉得adb命令很底层、很麻烦不如一些图形化的Profiler工具直观。这话没错Android Studio自带的Profiler确实强大又好看。但你想过没有当你需要长时间比如连续跑8小时压力测试监测性能或者需要在自动化测试流水线中集成性能检查点时图形化工具就有点力不从心了。这时候一串简单的adb命令或者一个几十行的Python脚本就成了你最可靠、最灵活的“瑞士军刀”。这篇文章我就想和你分享我这几年在实战中如何从最基础的adb命令开始一步步搭建起一套自动化性能监测体系。我们不谈空泛的理论就聊怎么用、怎么分析、怎么把数据变成优化你App性能的“行动指南”。哪怕你之前没怎么接触过adb跟着操作一遍也能立刻上手开始为你自己的项目“把脉问诊”。2. 你的第一把手术刀adb基础性能命令详解工欲善其事必先利其器。在写自动化脚本之前我们必须先熟悉手头的“兵器”。adb提供了几个核心命令能让我们像医生看X光片一样看清应用内部的运行状态。2.1 全局概览adb shell top命令top命令是Linux系统下的老牌性能查看工具在Android设备上同样可用。它给你一个实时的、动态刷新的系统进程资源占用排行榜。直接在命令行输入adb shell top你会看到类似下面这样不断刷新的信息PID USER PR NI VIRT RES SHR S %CPU %MEM TIME ARGS 12345 u0_a123 20 0 2.1G 180M 120M S 45.3 4.5 00:05:23 com.example.myapp 6789 root 20 0 1.8G 90M 60M S 12.1 2.2 00:12:34 system_server别被这一堆缩写吓到我们挑最重要的几个看PID进程ID。这是进程的唯一身份证后面过滤特定进程全靠它。%CPU这是最关键的指标之一。表示该进程的CPU占用率。这里有个容易误解的点这个百分比是相对于单个CPU核心的。如果你的手机是8核CPU一个线程满载一个核心这里就会显示100%。如果它用满了两个核心就会显示200%。所以看到超过100%的数字不要惊讶这很正常。%MEM内存占用率。指的是该进程使用的物理内存RES占设备总物理内存的百分比。RES实际使用的物理内存大小单位通常是KB或MB。这个值比VIRT虚拟内存更有参考价值因为它实实在在地占用了RAM。S进程状态。最常见的是S睡眠等待事件和R正在运行或可运行。如果你的应用主线程长时间处于R状态且%CPU很高那很可能在处理繁重任务需要优化。ARGS进程名通常就是应用的包名比如com.tencent.mm微信。光看全局信息太杂了我们通常只关心自己的应用。这时候可以用管道符|配合grepWindows下用findstr来过滤。比如我只想看微信的资源占用adb shell top | grep com.tencent.mm为了让数据更规整便于后续脚本处理top命令还支持丰富的参数。我常用的一个组合是adb shell top -d 1 -n 1 -s 6 -o PID,RES,%CPU,%MEM,ARGS-d 1设置刷新间隔为1秒。-n 1只采集一次数据就退出。这在写脚本循环采集时非常有用避免top自身持续运行占用资源。-s 6按第6列默认是%CPU列进行排序这样CPU占用最高的进程会排在最前面。-o PID,RES,%CPU,%MEM,ARGS自定义输出的列只显示我关心的几项让结果更简洁。2.2 内存深度剖析adb shell dumpsys meminfotop命令给了我们一个内存占用的概览RES但Android应用的内存世界要复杂得多。一个应用的内存由好几部分组成Java堆、Native堆、代码、资源文件等等。dumpsys meminfo命令就是用来做内存解剖的。针对特定应用包名执行adb shell dumpsys meminfo com.example.myapp输出的信息量很大我们聚焦在最核心的“App Summary”部分附近。你会看到类似下面的结构Applications Memory Usage (in Kilobytes): Uptime: 1234567890 Realtime: 1234567890 ** MEMINFO in pid 12345 [com.example.myapp] ** Pss Private Private Swapped Heap Heap Heap Total Dirty Clean Dirty Size Alloc Free ------ ------ ------ ------ ------ ------ ------ Native Heap 36840 36720 0 0 40960 38016 2944 Dalvik Heap 12345 12200 100 0 15360 12800 2560 .so 10500 8000 2000 0 .dex 5600 0 5600 0 .oat 3200 0 3200 0 .art 1800 1600 200 0 Other 2345 2000 300 0 TOTAL 73630 60520 11400 0 56320 50816 5504 App Summary Pss(KB) ------ Java Heap: 13545 Native Heap: 36840 Code: 19300 Stack: 256 Graphics: 12000 Private Other: 4567 System: 5122这里有几个黄金指标需要你牢牢记住PSS (Proportional Set Size)这是评估内存占用的首选指标。它计算了一个进程实际使用的物理内存并且公平地分摊了共享库占用的内存。比如三个App共用了一个10MB的共享库那么每个App的PSS会分摊大约3.3MB。这个值最接近系统认为的“你的应用占了多少内存”也是系统决定杀进程回收内存的主要依据。上面表格中的Pss Total就是它。Private Dirty这是你的应用独占的、不能被系统页面缓存的内存。这部分内存最“昂贵”因为它不能被其他进程复用一旦分配就实实在在地占着RAM。内存泄漏通常就发生在这里数值只增不减。Java Heap 和 Native Heap分别对应Java/Kotlin代码创建的对象和C/C代码或某些第三方库通过malloc分配的内存。如果这里持续增长很可能存在堆内存泄漏。Heap Alloc堆上已经分配的内存大小。它和Heap Size堆的总大小的比值可以反映出你的应用内存使用压力。如果Alloc长期接近SizeGC垃圾回收就会非常频繁导致卡顿。在实际排查内存问题时我通常会连续多次执行dumpsys meminfo命令观察PSS Total和Private Dirty的趋势。如果随着操作比如反复打开关闭某个页面这两个值阶梯式上涨且不回落那基本可以断定存在内存泄漏了。3. 从手动到自动构建你的第一个Python监控脚本手动敲命令只能看个瞬间状态性能问题往往是偶发的、需要长期观察的。把命令自动化才是解放生产力、发现深层问题的关键。下面我们就来手把手写一个Python脚本让它能像忠诚的哨兵一样持续为你记录应用的性能数据。3.1 脚本核心设计思路这个脚本的目标很明确定时比如每秒采集目标应用的CPU占用率和内存信息包括RES和PSS并记录到文件里同时计算并显示平均值和峰值。我们需要解决几个问题跨平台兼容团队里可能有人用Windows有人用macOS或Linux。adb命令本身是跨平台的但命令结果的过滤工具不同Windows用findstr类Unix系统用grep。稳定采集adb连接有时会不稳定脚本需要有简单的容错机制避免因一次采集失败就崩溃。数据可读采集的原始数据要保存同时最好能实时看到一个汇总的统计信息方便即时判断。灵活配置要监控哪个App包名、监控多长时间最好能通过参数来指定。基于这些我设计了一个脚本的基本结构。你可以先创建一个文件比如叫performance_monitor.py。3.2 代码逐行解析与实战我们先把脚本的骨架搭起来处理跨平台问题#!/usr/bin/env python # -*- coding: utf-8 -*- import os import time import sys import platform # 根据操作系统决定使用哪个过滤命令 if platform.system() Windows: # Windows 使用 findstr CMD_MEM_TEMPLATE adb shell dumpsys meminfo {} | findstr TOTAL CMD_CPU_TEMPLATE adb shell top -d 1 -n 1 -s 6 -o PID,RES,%CPU,%MEM,ARGS | findstr {} else: # macOS, Linux 使用 grep CMD_MEM_TEMPLATE adb shell dumpsys meminfo {} | grep TOTAL CMD_CPU_TEMPLATE adb shell top -d 1 -n 1 -s 6 -o PID,RES,%CPU,%MEM,ARGS | grep {}这里用了platform.system()来判断操作系统从而拼接不同的命令。{}是占位符后面会用具体的包名替换。接下来我们写一个执行命令并获取结果的函数。这里要注意adb shell返回的输出是字节流我们需要解码并处理成字符串。def execute_command(cmd): 执行shell命令并返回解析后的结果列表 try: # 使用os.popen执行命令read()读取所有输出 with os.popen(cmd) as pipe: output pipe.read().strip() if not output: return [] # 将输出按空白字符分割成列表方便提取数字 return output.split() except Exception as e: print(f执行命令失败: {cmd}, 错误: {e}) return []然后是脚本的核心循环监控函数。这个函数会接收包名和监控时长两个参数。def monitor_app(process_name, duration_seconds): 监控指定应用的性能 print(f开始监控应用: {process_name}, 时长: {duration_seconds} 秒) print(时间戳\t\tCPU(%)\tRES(MB)\tPSS(MB)) # 打开文件准备记录详细数据 log_file open(fperf_log_{process_name}_{int(time.time())}.txt, w) log_file.write(Timestamp\tCPU(%)\tRES(MB)\tPSS(MB)\n) # 初始化统计变量 cpu_values [] res_values [] pss_values [] start_time time.time() loop_count 0 # 主循环直到达到设定的监控时长 while (time.time() - start_time) duration_seconds: loop_count 1 current_time time.strftime(%H:%M:%S, time.localtime()) # 1. 获取CPU和RES信息 (来自top命令) cpu_cmd CMD_CPU_TEMPLATE.format(process_name) cpu_info execute_command(cpu_cmd) # 2. 获取PSS信息 (来自meminfo命令) mem_cmd CMD_MEM_TEMPLATE.format(process_name) mem_info execute_command(mem_cmd) # 检查命令是否成功返回了有效数据 if len(cpu_info) 5 and len(mem_info) 1: try: # 解析CPU占用率top命令的%CPU通常在第五列或第六列取决于列排序 # 我们之前用 -o 指定了顺序所以%CPU在索引2从0开始 cpu_usage float(cpu_info[2]) # 解析物理内存RES单位是KB我们转换为MB res_kb float(cpu_info[1][:-1]) # 去掉末尾的K或M if cpu_info[1].endswith(M): res_mb res_kb else: res_mb res_kb / 1024.0 # 解析PSSdumpsys meminfo输出的TOTAL行PSS值通常在第二列 pss_kb float(mem_info[1]) pss_mb pss_kb / 1024.0 # 收集数据 cpu_values.append(cpu_usage) res_values.append(res_mb) pss_values.append(pss_mb) # 实时打印和记录 print(f{current_time}\t{cpu_usage:.1f}%\t{res_mb:.1f}MB\t{pss_mb:.1f}MB) log_file.write(f{current_time}\t{cpu_usage:.1f}\t{res_mb:.1f}\t{pss_mb:.1f}\n) except (IndexError, ValueError) as e: print(f{current_time} - 数据解析出错: {e}原始数据: cpu_info{cpu_info}, mem_info{mem_info}) else: print(f{current_time} - 未找到进程信息或命令执行失败。请确认应用正在运行且包名正确。) # 每次采集后等待1秒 time.sleep(1) # 监控结束计算统计信息 end_time time.time() total_duration end_time - start_time if cpu_values: avg_cpu sum(cpu_values) / len(cpu_values) max_cpu max(cpu_values) avg_res sum(res_values) / len(res_values) max_res max(res_values) avg_pss sum(pss_values) / len(pss_values) max_pss max(pss_values) print(\n 监控结果统计 ) print(f监控时长: {total_duration:.0f} 秒 采集次数: {loop_count}) print(fCPU - 平均: {avg_cpu:.1f}%, 峰值: {max_cpu:.1f}%) print(f内存(RES) - 平均: {avg_res:.1f}MB, 峰值: {max_res:.1f}MB) print(f内存(PSS) - 平均: {avg_pss:.1f}MB, 峰值: {max_pss:.1f}MB) print(f详细日志已保存至: {log_file.name}) else: print(未采集到有效数据。) log_file.close()最后我们添加主函数入口让它支持命令行参数运行if __name__ __main__: # 默认监控微信10秒如果提供了命令行参数则使用参数 default_app com.tencent.mm # 微信包名 default_duration 10 if len(sys.argv) 3: target_app sys.argv[1] try: target_duration int(sys.argv[2]) except ValueError: print(错误时长参数必须是整数秒) sys.exit(1) else: print(f未提供参数使用默认设置应用{default_app}, 时长{default_duration}秒) target_app default_app target_duration default_duration # 开始监控 monitor_app(target_app, target_duration)现在这个脚本就可以用了。保存后在命令行里这样运行# 监控微信持续60秒 python performance_monitor.py com.tencent.mm 60 # 监控你自己的应用 python performance_monitor.py com.yourcompany.yourapp 120脚本运行后控制台会每秒打印一行当前数据监控结束后会给出平均值和峰值。同时所有原始数据都会保存在一个以时间戳命名的文本文件里方便你后续用Excel或Python的pandas库做进一步分析比如绘制趋势图。4. 进阶实战让监控脚本更强大、更智能基础的监控脚本跑起来后你已经能发现很多问题了。但要想把它集成到自动化测试流程或者进行更专业的分析我们还需要给它增加一些“肌肉”。4.1 应对复杂场景多进程应用与GPU监控很多现代应用尤其是游戏或大型社交App都不止一个进程。比如微信就有主进程、工具进程、推送进程等。我们的脚本需要能同时监控多个相关进程。思路很简单把单个包名的监控改成一个进程名列表的监控。我们可以修改monitor_app函数让它接收一个列表。在循环里对列表中的每个进程名都执行一次top和meminfo命令然后把它们的数据分别记录或汇总。更实际的需求是监控GPU渲染和电量消耗。卡顿不仅来自CPU也可能来自GPU渲染一帧的时间过长掉帧。我们可以用adb shell dumpsys gfxinfo package_name命令来获取应用的帧耗时信息。这个命令的输出需要解析Profile data in ms部分计算每一帧的渲染时间是否超过16.67ms60FPS的标准。把这个逻辑集成到脚本里你就能同时监控应用的流畅度了。电量监控则更复杂一些可以通过adb shell dumpsys batterystats命令来获取粗略的耗电情况但对于精细化的功耗分析通常需要借助更专业的硬件工具或系统级API。4.2 数据可视化从日志到图表文本日志虽然详细但不够直观。人眼对图形的敏感度远高于数字。我们可以用Python强大的绘图库matplotlib在监控结束后自动生成性能趋势图。假设我们已经把每次采集的数据时间戳、CPU、RES、PSS都存到了一个列表或文件里。监控结束后可以添加这样一段代码import matplotlib.pyplot as plt import pandas as pd # 假设data_list是一个包含字典的列表每个字典有time, cpu, res, pss键 # 或者我们从之前保存的日志文件读取 df pd.read_csv(perf_log_com.tencent.mm_1234567890.txt, sep\t) # 创建图表 fig, axes plt.subplots(3, 1, figsize(12, 10)) fig.suptitle(f应用性能监控报告 - {process_name}) axes[0].plot(df[CPU(%)], labelCPU占用率, colorred) axes[0].axhline(yavg_cpu, colorr, linestyle--, labelf平均: {avg_cpu:.1f}%) axes[0].set_ylabel(CPU (%)) axes[0].legend() axes[0].grid(True) axes[1].plot(df[RES(MB)], label物理内存(RES), colorblue) axes[1].axhline(yavg_res, colorb, linestyle--, labelf平均: {avg_res:.1f}MB) axes[1].set_ylabel(RES (MB)) axes[1].legend() axes[1].grid(True) axes[2].plot(df[PSS(MB)], label比例内存(PSS), colorgreen) axes[2].axhline(yavg_pss, colorg, linestyle--, labelf平均: {avg_pss:.1f}MB) axes[2].set_ylabel(PSS (MB)) axes[2].set_xlabel(采样点 (每秒一次)) axes[2].legend() axes[2].grid(True) plt.tight_layout() plt.savefig(fperformance_chart_{process_name}.png, dpi300) print(f性能图表已保存为 performance_chart_{process_name}.png) # plt.show() # 如果你在桌面环境运行可以显示图表生成一张如下图所示的图表性能的波动、峰值、增长趋势就一目了然了。你可以清晰地看到在用户执行某个操作比如滑动列表时CPU出现了尖峰在进入某个页面后内存出现了阶梯式上涨且未回落。这些图表是向团队汇报性能问题、证明优化效果的最有力证据。4.3 集成到CI/CD自动化性能门禁脚本和图表都有了如何让它发挥最大价值答案是集成到持续集成/持续部署CI/CD流水线中作为一道“性能门禁”。具体做法是在你的自动化测试框架比如基于Appium或UIAutomator2的UI测试中在关键测试用例例如首页加载、核心业务流程执行前后调用我们的监控脚本。设定一些性能基线Baseline作为阈值CPU平均占用率不超过X%内存PSS峰值不超过Y MB单次操作后内存增长在Z秒内应回落如果测试结果超过了这些阈值就让CI任务标记为“不稳定”甚至“失败”并自动将性能报告日志图表附在构建通知里。这样任何导致性能退化的代码提交在合入主分支前就会被发现。这比等到线上用户投诉后再来回溯排查效率要高得多成本也低得多。要实现这一点你需要将Python脚本封装成一个更通用的模块或类提供清晰的函数接口方便被其他测试脚本调用。同时阈值可以放在配置文件里方便不同项目、不同版本进行调整。5. 避坑指南与性能分析心法工具用熟了最后拼的就是经验和思路。这里分享几个我在实战中踩过的坑和总结的分析心法希望能帮你少走弯路。第一个大坑adb连接不稳定。在长时间监控或自动化测试中adb连接可能会意外断开。一个健壮的脚本必须处理这种情况。我通常会在execute_command函数里加入重试机制如果命令执行失败返回空或超时尝试重新执行1-2次。更彻底的做法是在脚本开始时先执行adb kill-server和adb start-server来重启adb服务确保连接干净。第二个坑数据波动与干扰。性能数据本身就有波动性尤其是CPU。一次偶然的GC垃圾回收或者系统后台任务都可能引起一个短暂的尖峰。所以不要只看单点数据一定要看趋势和平均值。这也是为什么我们的脚本要计算平均值和持续记录的原因。分析时重点关注那些持续高位或者呈现明显上升趋势的指标。第三个坑测试环境不“干净”。在性能测试前务必重启应用甚至重启手机确保从一个干净的状态开始。后台运行的其他应用尤其是竞品会严重干扰结果。最好使用纯净的测试机或者通过adb shell am force-stop命令在测试前清理后台。分析心法从宏观到微观层层递进。先看整体趋势用脚本跑一遍核心场景看CPU和内存的整体曲线。有没有异常的“毛刺”或“台阶”定位问题点如果发现某个操作后内存台阶式上涨就反复执行这个操作比如打开/关闭同一个页面5-10次观察内存是否每次上涨都不回落。这就是典型的内存泄漏测试方法。使用更精细的工具深挖当脚本帮你定位到大概的问题范围比如某个页面或操作后就该祭出更强大的工具了。用Android Studio的Memory Profiler连接设备在怀疑泄漏的时间点手动抓取Heap Dump堆转储然后分析对象引用链精准找到是哪个对象被意外持有了。用CPU Profiler录制方法跟踪找到消耗CPU最多的“热点”方法。对比与回归修复问题后用同样的脚本、在同样的环境下再次测试对比修复前后的性能数据。只有数据上的改善才能证明你的优化是真正有效的。性能优化是一个持续的过程而不是一次性的任务。把这个自动化的监控脚本作为你的日常工具把它融入到开发流程中你会对自己应用的状态了如指掌也能在用户反馈之前就提前发现并解决潜在的性能风险。