ADB与移动端专项相关
ADB 与移动端专项测试 完整系统学习大纲
本文面向测试工程师,按”ADB 基础 → 命令体系 → 日志分析 → 各专项维度 → 自动化脚本”的顺序组织,由浅入深。 功能测试保证”能用”,专项测试保证”好用”:启动快不快、卡不卡、耗不耗电、弱网会不会崩、内存会不会涨。ADB 是做这一切的地基工具——它不需要你在 App 里加任何代码,就能拿到系统级的运行数据。
一、移动端测试概述
1.1 移动端测试 vs Web 测试
| 维度 | Web | 移动端 App |
|---|---|---|
| 环境 | 浏览器(Chrome/Firefox…) | 系统版本 + 厂商 ROM + 机型碎片化严重 |
| 安装 | 无需安装,刷新即生效 | 需下载安装,涉及升级/覆盖/卸载场景 |
| 网络 | 多为有线/稳定 WiFi | 2G/3G/4G/5G/WiFi 切换,弱网与断网是常态 |
| 交互 | 鼠标键盘 | 触摸、手势、重力感应、语音、摄像头 |
| 中断 | 基本没有 | 来电、短信、切后台、锁屏、低电量随时发生 |
| 资源 | 内存/CPU 几乎不用管 | 内存、电量、流量、发热都是硬约束 |
| 更新 | 服务端更新即生效 | 客户端发版周期长,老版本长期存在 |
1.2 专项测试包含哪些维度
移动端专项测试
├── 稳定性专项 崩溃(Crash)、ANR(无响应)
├── 性能专项 启动耗时、流畅度(FPS/卡顿)、内存、CPU
├── 资源专项 耗电量、流量消耗
├── 网络专项 弱网、断网重连、网络切换
├── 兼容专项 系统版本、厂商 ROM、分辨率、折叠屏
├── 安装专项 安装、覆盖安装、降级、卸载
├── 中断专项 来电、短信、切后台、锁屏、旋转
├── 安全专项 权限、组件导出、日志泄露、敏感信息
└── 其他专项 推送、国际化、无障碍、耗电发热
1.3 ADB 在其中的位置
ADB(Android Debug Bridge) 是 Android SDK 自带的调试桥,是移动端测试最基础、最高频的工具:
- 查看设备与进程状态
- 安装/卸载/启动应用
- 抓取日志(崩溃、ANR 定位的核心手段)
- 采集性能数据(启动耗时、内存、CPU、FPS、流量、电量)
- 模拟用户操作(点击、滑动、输入)——自动化脚本的底层
- 文件传输、截图录屏
💡 ADB 的最大优势:不需要 App 配合、不需要源码、不需要 root(大部分命令),装上 SDK 就能用。
二、ADB 环境搭建
2.1 ADB 架构(理解原理才能排错)
┌──────────── PC ────────────┐ ┌──────── 手机 ────────┐
│ │ │ │
│ adb client (命令行) │ USB/WiFi │ │
│ ↓ │ ◄────────► │ adbd (守护进程) │
│ adb server (端口 5037) │ │ │
│ │ │ │
└────────────────────────────┘ └──────────────────────┘
- client:你在终端敲的
adb命令。 - server:PC 上的后台进程,管理 client 与设备的通信(端口 5037)。
- adbd:手机上的守护进程,真正执行命令。
# server 出问题时重启它(连不上设备的万能解法)
adb kill-server
adb start-server
2.2 安装配置
# 方式一:装完整 Android Studio(体积大,但带模拟器和全套工具)
# 方式二:只装命令行工具(推荐,测试同学够用)
# 下载 platform-tools:https://developer.android.com/tools/releases/platform-tools
# 解压后把目录加入 PATH
# Windows:系统变量 Path 增加 D:\Android\platform-tools
# macOS/Linux:
echo 'export PATH=$PATH:~/Android/platform-tools' >> ~/.zshrc
source ~/.zshrc
# 验证
adb version
手机端准备:
- 设置 → 关于手机 → 连续点击「版本号」7 次 → 开启开发者模式
- 设置 → 系统 → 开发者选项 → 打开「USB 调试」
- 数据线连接电脑,手机上点「允许 USB 调试」(勾选”始终允许”)
- Windows 可能需要装驱动(各厂商官网下载;大部分 Win10+ 自动识别)
2.3 连接设备
adb devices # 查看已连接设备
adb devices -l # 显示详细信息(型号、transport_id)
设备状态说明:
| 状态 | 含义 | 处理 |
|---|---|---|
device |
正常连接 | ✅ |
unauthorized |
手机上没授权 | 手机端确认”允许 USB 调试”弹窗 |
offline |
设备离线/adbd 未就绪 | 重插数据线、adb kill-server、检查授权 |
| 无输出 | 没识别到 | 检查数据线(很多线只能充电)、驱动、USB 调试开关 |
多设备时指定目标(重要,多机并行测试必用):
adb -s <serial> <command> # 按序列号
adb -s emulator-5554 shell
adb -d <command> # 唯一 USB 设备
adb -e <command> # 唯一模拟器
WiFi 无线连接(摆脱数据线,长时间稳定性测试很有用):
# Android 10 及以下
adb tcpip 5555 # USB 连着时执行,切换为 TCP 模式
adb connect 192.168.1.100:5555 # 连接手机 IP
adb disconnect 192.168.1.100:5555
# Android 11+(需要配对码)
adb pair 192.168.1.100:4444 # 手机"无线调试"里点"使用配对码配对"
adb connect 192.168.1.100:5555
⚠️ 无线调试要求手机与电脑同一局域网。测稳定性时用它,避免数据线松动导致测试中断。
三、ADB 命令体系
3.1 设备信息类
# 基本属性(品牌、型号、系统版本、分辨率、ABI)
adb shell getprop ro.product.brand
adb shell getprop ro.product.model
adb shell getprop ro.build.version.release # Android 版本,如 13
adb shell getprop ro.build.version.sdk # API Level,如 33
adb shell cat /system/build.prop # 全部属性
# 屏幕信息
adb shell wm size # 分辨率 1080x2400
adb shell wm density # 密度 dpi
adb shell dumpsys window displays # 详细显示信息
# 电池
adb shell dumpsys battery # 电量、充电状态、温度
# 磁盘/内存概况
adb shell df -h
adb shell free -m
3.2 应用管理类(高频)
# 安装
adb install app.apk
adb install -r app.apk # 覆盖安装(保留数据)★ 测试升级必用
adb install -t app.apk # 允许安装 debug/测试包
adb install -d app.apk # 允许降级安装(需签名一致且 debuggable)
adb install -g app.apk # 安装后自动授予所有运行时权限
# 卸载
adb uninstall com.example.app
adb uninstall -k com.example.app # 卸载但保留数据和缓存 ★ 测"重装后数据"场景
# 清除数据(等价于设置里的"清除数据",回到首次安装状态)
adb shell pm clear com.example.app
# 查看包信息
adb shell pm list packages # 所有包
adb shell pm list packages -3 # 只看第三方
adb shell pm list packages | grep example # 过滤
adb shell pm path com.example.app # APK 安装路径
adb shell dumpsys package com.example.app # 包的完整信息(权限、版本、组件)
# 版本信息
adb shell dumpsys package com.example.app | grep versionName
💡 测试场景:
adb install -r验证覆盖安装(数据保留、登录态是否还在);adb uninstall -k+ 重装验证数据目录残留;pm clear用于把 App 恢复到干净状态再跑用例。
3.3 启动与进程类
# 启动应用(需知道入口 Activity)
adb shell am start -n com.example.app/.MainActivity
adb shell am start -n com.example.app/com.example.app.SplashActivity
# 启动并测量耗时(性能专项核心命令,见第六章)
adb shell am start -W -n com.example.app/.MainActivity
# 强制停止(相当于"强行停止",冷启动测试前必做)
adb shell am force-stop com.example.app
# 通过 URL 启动(测试 Deep Link / 分享链接)
adb shell am start -a android.intent.action.VIEW -d "https://example.com/detail?id=1"
# 发送广播
adb shell am broadcast -a com.example.app.ACTION_TEST
# 查看进程
adb shell ps | grep com.example
adb shell ps -A | grep example
# 杀进程(按包名,等价于 force-stop 的底层实现)
adb shell am kill com.example.app
获取入口 Activity 的方法:
# 方法一:先把 App 打开到前台,再看当前窗口
adb shell dumpsys window | grep mCurrentFocus
adb shell dumpsys activity activities | grep mResumedActivity
# 方法二:用 monkey 触发(不需要知道 Activity)
adb shell monkey -p com.example.app 1
# 方法三:从 APK 解析(aapt 工具)
aapt dump badging app.apk | grep launchable-activity
3.4 文件传输类
# 电脑 → 手机
adb push local.txt /sdcard/
adb push ./testdata/ /sdcard/testdata/
# 手机 → 电脑
adb pull /sdcard/screenshot.png ./
adb pull /data/anr/traces.txt ./ # ANR 日志(可能需要 root)
# 在手机上操作文件
adb shell ls /sdcard/
adb shell rm /sdcard/test.txt
adb shell cat /sdcard/config.txt
⚠️ 权限说明:
/sdcard/(内部存储)可自由读写;/data/data/<包名>/是应用私有目录,非 root 无法访问(Android 7+ 尤其严格)。需要取私有数据时,让开发开 debug 包或用run-as(仅 debug 包可用):adb shell run-as com.example.app cat /data/data/com.example.app/shared_prefs/config.xml
3.5 截图与录屏(提 Bug 必备)
# 截图
adb shell screencap -p /sdcard/screen.png
adb pull /sdcard/screen.png ./
# 一行完成(截图直接存电脑)
adb exec-out screencap -p > screen.png # 推荐,无需中转
# 录屏(默认 180 秒,Ctrl+C 提前结束)
adb shell screenrecord /sdcard/demo.mp4
adb shell screenrecord --time-limit 10 /sdcard/demo.mp4 # 限制 10 秒
adb shell screenrecord --size 720x1280 --bit-rate 4000000 /sdcard/demo.mp4
adb pull /sdcard/demo.mp4 ./
💡 测试用途:录屏是复现偶现问题和分析启动耗时(逐帧看首屏出现时间)的关键手段,比截图信息量大得多。
3.6 模拟用户操作(自动化基础)
# 点击(坐标)
adb shell input tap 500 1200
# 滑动(起点 → 终点,可带时长 ms)
adb shell input swipe 300 1000 300 500 # 上滑
adb shell input swipe 300 1000 300 500 500 # 慢速滑动(模拟真实手势)
# 输入文本(不支持中文!中文需用输入法广播,见下)
adb shell input text "hello123"
# 按键
adb shell input keyevent 3 # KEYCODE_HOME
adb shell input keyevent 4 # KEYCODE_BACK
adb shell input keyevent 26 # KEYCODE_POWER(锁屏/唤醒)
adb shell input keyevent 82 # KEYCODE_MENU
adb shell input keyevent 66 # KEYCODE_ENTER
adb shell input keyevent 67 # KEYCODE_DEL(删除)
常用 KeyEvent 对照:
| 值 | 含义 | 值 | 含义 |
|---|---|---|---|
| 3 | HOME | 26 | 电源键 |
| 4 | 返回 | 82 | 菜单 |
| 187 | 最近任务 | 164 | 静音 |
| 24 / 25 | 音量+ / 音量- | 66 | 回车 |
| 67 | 删除 | 61 | Tab |
输入中文(绕开 input text 限制):
# 借助 ADBKeyBoard 输入法(需先在手机安装并启用)
adb shell am broadcast -a ADB_INPUT_TEXT --es msg "中文测试"
adb shell am broadcast -a ADB_INPUT_CODE --ei code 67 # 删除
3.7 其他实用命令
# 端口转发(访问手机上的服务,或配合抓包代理)
adb forward tcp:8080 tcp:8080
adb reverse tcp:8888 tcp:8888 # 反向:手机访问电脑的 8888(代理抓包常用)
# 查看 Activity 栈
adb shell dumpsys activity activities
# 模拟低电量(部分版本支持)
adb shell dumpsys battery set level 5
adb shell dumpsys battery reset # 恢复真实状态
# 清空 logcat 缓冲区(开始新测试前必做,避免旧日志干扰)
adb logcat -c
# 重启设备
adb reboot
adb reboot recovery
adb reboot bootloader
四、logcat 日志分析(核心技能)
4.1 logcat 基础
adb logcat # 实时输出全部日志
adb logcat -c # 清空缓冲区(测试开始前先执行)
adb logcat -d # 一次性输出当前缓冲区内容后退出
adb logcat > log.txt # 保存到文件(长时间测试用)
adb logcat -v time # 显示时间
adb logcat -v threadtime # 显示时间+线程+进程(推荐,默认格式)
4.2 日志级别
| 级别 | 标识 | 含义 |
|---|---|---|
| V | Verbose | 最详细(默认不显示) |
| D | Debug | 调试信息 |
| I | Info | 一般信息 |
| W | Warn | 警告 |
| E | Error | 错误(重点看) |
| F | Fatal | 致命错误 |
adb logcat *:E # 只看 Error 及以上
adb logcat *:W # 只看 Warn 及以上
adb logcat MyTag:D *:S # 只看 MyTag 的 Debug 及以上,其他静默
4.3 过滤技巧(实战高频)
# 按 tag 过滤(-s = silent,只显示指定 tag)
adb logcat -s ActivityManager
adb logcat -s AndroidRuntime # 崩溃日志都在这里
# 按进程过滤(Android 7+)
adb logcat --pid=$(adb shell pidof com.example.app)
# 组合 grep(Windows 用 findstr)
adb logcat | grep -i "exception"
adb logcat | grep -E "FATAL|ANR|died"
adb logcat | grep --line-buffered com.example.app
# 按关键字高亮排除干扰
adb logcat | grep -v "Choreographer" # 排除刷新日志
💡 提 Bug 的标准动作:
adb logcat -c→ 复现问题 →adb logcat -d > bug.log→ 从里面捞出崩溃堆栈,附到缺陷单。开发拿到堆栈基本能直接定位。
4.4 日志缓冲区
adb logcat -b main # 应用日志(默认)
adb logcat -b system # 系统日志
adb logcat -b events # 事件日志(Activity 生命周期等)
adb logcat -b crash # 崩溃日志
adb logcat -b all # 全部
五、崩溃与 ANR 专项(重点)
5.1 三种”挂”的类型
| 类型 | 表现 | 原因 |
|---|---|---|
| Java Crash | 应用闪退,弹”XXX 已停止运行” | 未捕获异常(NullPointer、IndexOutOfBounds…) |
| Native Crash | 闪退,无提示或提示不明确 | so 库崩溃(内存越界、空指针) |
| ANR | 弹”应用无响应”,可选等待/关闭 | 主线程被阻塞 |
5.2 崩溃日志定位
# 方式一:logcat 抓异常(最常用)
adb logcat | grep -i "FATAL EXCEPTION"
adb logcat -s AndroidRuntime
# 典型 Java 崩溃日志结构
# E AndroidRuntime: FATAL EXCEPTION: main
# E AndroidRuntime: Process: com.example.app, PID: 12345
# E AndroidRuntime: java.lang.NullPointerException:
# E AndroidRuntime: at com.example.app.MainActivity.onCreate(MainActivity.java:42)
# ↑ 直接定位到代码行
# 方式二:查看崩溃缓冲区
adb logcat -b crash
# 方式三:dropbox(系统保存的历史崩溃,可看已发生的)
adb shell ls /data/system/dropbox/
adb shell dumpsys dropbox --print data_app_crash
关键字段:
FATAL EXCEPTION: main—— 崩溃发生在主线程java.lang.XXXException—— 异常类型at com.xxx.ClassName.method(File.java:行号)—— 最上面几行就是崩溃点
5.3 ANR 定位
ANR 触发阈值(主线程超时):
| 场景 | 超时时间 |
|---|---|
| 输入事件(点击/触摸) | 5 秒 |
| BroadcastReceiver | 前台 10 秒 / 后台 60 秒 |
| Service 生命周期 | 20 秒 |
| ContentProvider | 10 秒 |
常见原因:
- 主线程做网络请求、数据库读写、大量计算
- 主线程被同步锁阻塞(死锁、Binder 调用等待)
- CPU 被其他进程占满 / IO 压力大
# 方式一:logcat 抓 ANR 关键字
adb logcat | grep "ANR in"
# 典型输出:
# E ActivityManager: ANR in com.example.app (com.example.app/.MainActivity)
# E ActivityManager: Reason: Input dispatching timed out
# E ActivityManager: CPU usage from 0ms to 5000ms later: ...
# 方式二:导出 traces 文件(含各线程堆栈,最有力的证据)
adb shell ls /data/anr/
adb pull /data/anr/traces.txt ./
# Android 10+ 权限收紧,普通应用无法直接读取,改用:
adb bugreport bugreport.zip # 完整报告,解压后 FS/data/anr/ 下有 traces
# 方式三:dropbox
adb shell dumpsys dropbox --print data_app_anr
traces.txt 怎么读:
----- pid 12345 at 2026-10-27 10:00:00 -----
Cmd line: com.example.app
"main" prio=5 tid=1 WAIT ← 主线程状态
at java.lang.Object.wait(...)
at com.example.app.MainActivity.loadData(MainActivity.java:88)
↑ 停在这里 → 说明 loadData 卡住了主线程
"Binder:12345_2" prio=5 tid=12 RUNNABLE
...
💡 给开发的 Bug 描述模板:ANR 时附上 traces 文件 + logcat 中
CPU usage那几行 + 复现步骤,三件套齐全开发基本不用再问。
5.4 稳定性指标
| 指标 | 计算 | 业界常见标准 |
|---|---|---|
| 崩溃率 | 崩溃次数 / 启动次数 | < 0.2%(优秀 < 0.1%) |
| ANR 率 | ANR 次数 / 启动次数 | < 0.1% |
| Crash 用户覆盖率 | 崩溃用户数 / 活跃用户数 | < 0.3% |
结合 Monkey 做稳定性压测,详见 Monkey稳定性测试。
六、启动耗时专项
6.1 三种启动类型
| 类型 | 场景 | 耗时量级 |
|---|---|---|
| 冷启动 | 进程不存在,从头创建(首次启动/被杀后) | 最长,重点指标 |
| 温启动 | 进程在,Activity 需重建(退到后台很久) | 中等 |
| 热启动 | Activity 还在后台(按 Home 再回来) | 最短 |
6.2 测量方法
方法一:am start -W(最简单,测冷启动)
# 先强制停止,确保是冷启动
adb shell am force-stop com.example.app
sleep 2
adb shell am start -W -n com.example.app/.MainActivity
# 输出示例:
# Status: ok
# Activity: com.example.app/.MainActivity
# ThisTime: 856
# TotalTime: 856
# WaitTime: 873
# Complete
| 字段 | 含义 |
|---|---|
| ThisTime | 最后一个 Activity 启动耗时 |
| TotalTime | 所有 Activity 启动耗时(一般取这个) |
| WaitTime | AMS 启动 Activity 的总耗时(含前一个应用 pause) |
方法二:logcat 的 Displayed 时间(无需知道 Activity 名)
adb logcat -c
# 手动或脚本启动 App
adb logcat | grep "Displayed"
# 输出:
# I ActivityManager: Displayed com.example.app/.MainActivity: +1s234ms
# 系统自动打印,Android 4.4+ 支持,最直观
方法三:录屏逐帧分析(最贴近用户感知)
adb shell screenrecord --time-limit 5 /sdcard/start.mp4
# 用播放器逐帧查看:从点击图标 → 首屏内容完全显示 = 真实启动耗时
6.3 验收标准与优化方向
| 应用类型 | 冷启动参考值 |
|---|---|
| 轻量级工具 | < 800 ms |
| 一般 App | < 1.5 s |
| 大型 App(电商/社交) | < 2 s |
常见优化点(测试时关注):
- Application 里做了太多初始化(三方 SDK 集中初始化)
- 启动页广告/图加载阻塞
- 首页数据接口串行请求
- 首屏布局层级过深
💡 测试提示:启动耗时必须多测几次取中位数(首次启动有类加载、IO 缓存等干扰,第一次通常偏慢 30%+)。
七、流畅度与卡顿专项
7.1 基础概念
- 帧率(FPS):每秒渲染帧数。Android 标准是 60 FPS,即每帧 16.67 ms。
- 卡顿(Jank):一帧渲染超过 16.67 ms,就会掉帧,用户感知为”卡”。
- 高刷屏(90/120Hz)对应每帧 11.1ms / 8.3ms。
7.2 采集方法
# 方法一:gfxinfo 帧统计(最常用)
adb shell dumpsys gfxinfo com.example.app
# 输出关键部分:
# Total frames rendered: 1200
# Janky frames: 45 (3.75%) ← 卡顿帧占比,核心指标
# 50th percentile: 6ms
# 90th percentile: 12ms
# 95th percentile: 20ms ← 超过 16.67ms 说明有卡顿
# 99th percentile: 48ms
# 重置统计(开始测试前执行,避免累计旧数据)
adb shell dumpsys gfxinfo com.example.app reset
# 详细帧数据(每帧的时间戳)
adb shell dumpsys gfxinfo com.example.app framestats
# 方法二:SurfaceFlinger 延迟数据(更底层)
adb shell dumpsys SurfaceFlinger --latency com.example.app/.MainActivity
# 输出三列:请求时间 / 绘制开始 / 绘制完成(单位 ns)
# 方法三:开发者选项 → "GPU 呈现模式分析" → "在屏幕上显示为条形图"
# 直观看到每帧耗时,绿色横线是 16.67ms 基准线
7.3 判定标准
| 指标 | 良好 | 需优化 |
|---|---|---|
| Janky frames 占比 | < 5% | > 10% |
| 95th percentile 帧耗时 | < 16.67 ms | > 33 ms(掉 2 帧以上) |
常见卡顿原因(提 Bug 时写清楚场景):
- 主线程做耗时操作(IO、JSON 解析、图片解码)
- 列表滑动时频繁创建对象(触发 GC)
- 布局层级过深 / 过度绘制
- 频繁 GC(
adb logcat | grep GC看 GC 频率)
💡 测试要点:卡顿测试一定要在真实操作场景下测(列表快速滑动、页面切换动画、复杂列表刷新),空页面滑动是没意义的。
八、内存与 CPU 专项
8.1 内存采集
# 核心命令:meminfo
adb shell dumpsys meminfo com.example.app
# 输出关键指标(单位 KB):
# Pss Total: 85632 ← 主要看这个
# Private Dirty: 78210
# Heap Size: 45120
# Heap Alloc: 38900
# Heap Free: 6220
#
# Views: 45 Activities: 3 ← Activity 数量异常增长 = 泄漏信号
| 指标 | 含义 |
|---|---|
| PSS | 实际使用的物理内存(按比例分摊共享库),主要指标 |
| Private Dirty | 进程私有、被修改过的内存 |
| Heap Size/Alloc | Java 堆大小/已分配 |
| Views / Activities | 视图与 Activity 实例数(泄漏监控关键) |
# 简洁模式(只看总 PSS)
adb shell dumpsys meminfo com.example.app | grep "TOTAL"
# 系统整体内存
adb shell cat /proc/meminfo
# MemTotal / MemAvailable 判断设备是否内存紧张
# 内存快照对比(泄漏检测)
adb shell dumpsys meminfo com.example.app > mem_before.txt
# ...反复操作某页面 20 次后...
adb shell dumpsys meminfo com.example.app > mem_after.txt
# 对比 PSS 与 Activities 数量
8.2 内存泄漏判断要点
| 现象 | 判断 |
|---|---|
| 反复进出某页面,PSS 持续增长且不回落 | 疑似泄漏 |
Activities 数量只增不减 |
Activity 泄漏(静态引用、未注销监听、Handler 延迟消息) |
| 退到后台后 PSS 明显高于初始值 | 缓存未释放 |
精确泄漏定位用 LeakCanary(需开发集成)或 Android Studio Profiler。测试阶段做到”发现数值异常”就够,定位交给开发。
8.3 CPU 采集
# 实时 CPU 占用
adb shell top -n 1 | grep com.example
adb shell top -m 10 # CPU 占用最高的 10 个进程
# 一段时间内的 CPU 统计
adb shell dumpsys cpuinfo | grep com.example
# 单进程 CPU(/proc 方式)
adb shell cat /proc/<pid>/stat
CPU 异常的表现与原因:
- 退到后台 CPU 仍很高 → 后台有死循环/定时任务没停
- 滑动时 CPU 100% → 主线程过度计算
- 发热严重 → CPU 长期高负载
九、流量与电量专项
9.1 流量采集
# 方法一:按 UID 统计(推荐)
# 先查应用的 uid
adb shell dumpsys package com.example.app | grep userId
# 输出:userId=10123
# 查看收发字节(部分版本路径可用)
adb shell cat /proc/uid_stat/10123/tcp_rcv # 接收字节
adb shell cat /proc/uid_stat/10123/tcp_snd # 发送字节
# 方法二:netstats(通用性更好)
adb shell dumpsys netstats | grep -A 5 "uid=10123"
# 方法三:xt_qtaguid(Android 8+ 常用)
adb shell cat /proc/net/xt_qtaguid/stats | grep 10123
流量测试步骤:
# 1. 记录初始值
adb shell cat /proc/uid_stat/10123/tcp_rcv > rcv_start.txt
# 2. 执行被测场景(如浏览首页 10 分钟)
# 3. 记录结束值
# 4. 差值 = 场景流量消耗
| 测试点 | 关注 |
|---|---|
| 首次启动流量 | 是否过大(资源预加载) |
| 重复请求相同数据 | 是否做了缓存(第二次应明显减少) |
| 后台流量 | 退到后台是否还在偷偷请求 |
| 图片加载 | 是否按屏幕尺寸压缩、是否懒加载 |
| 断网重连 | 是否重复拉取大量数据 |
9.2 电量采集
# 重置统计(开始测试前必须执行)
adb shell dumpsys batterystats --reset
# 查看电池状态
adb shell dumpsys battery
# 输出:level(电量) / status(充电状态) / temperature(温度) / health
# 导出耗电统计
adb shell dumpsys batterystats > battery.txt
# Android 7+ 推荐用 bugreport 导出完整数据
adb bugreport bugreport.zip
# 查看耗电排行
adb shell dumpsys batterystats | grep -i "Estimated power use"
耗电测试要点:
| 测试点 | 关注 |
|---|---|
| 后台耗电 | 锁屏 30 分钟后耗电是否异常 |
| 定位 | 是否持续使用 GPS(最耗电的传感器之一) |
| 唤醒锁(WakeLock) | 是否持有未释放 → 屏幕关了 CPU 不休眠 |
| 网络请求频率 | 心跳包/轮询间隔是否过短 |
| 传感器 | 退出页面后是否注销监听 |
💡 精确耗电分析可用 Google 的 Battery Historian(解析 bugreport 生成可视化报告),但测试阶段用系统”设置 → 电池”看耗电排行 +
batterystats数值对比已经能发现大部分问题。
十、弱网与网络专项
10.1 弱网指标
| 指标 | 说明 | 常见取值 |
|---|---|---|
| 带宽 | 单位时间传输量 | 50 kbps(2G)~ 4 Mbps(4G) |
| 延迟 | 往返时间 RTT | 50 ms ~ 2000 ms |
| 丢包率 | 丢包比例 | 0% ~ 10% |
| 抖动 | 延迟波动 | 影响实时性 |
10.2 模拟方法
| 方法 | 工具 | 适用 |
|---|---|---|
| 代理限速 | Charles / Fiddler Throttle | ✅ 最常用,详见 抓包工具 |
| 网络损伤仪 | Facebook ATC、Network Link Conditioner | 团队共享,参数更真实 |
| 模拟器限速 | Android Emulator 自带网络设置 | 快速验证 |
| 开发者选项 | 部分 ROM 有”网络限速” | 因机型而异 |
| 真实场景 | 电梯/地铁/地下车库实测 | 补充验证 |
10.3 测试点与验收
- 弱网下能否加载出页面(不能白屏/一直转圈)
- 是否有超时提示 + 重试按钮(不能无限等待)
- 超时时间是否合理(一般 15~30 秒)
- 断网后是否有友好提示(不能闪退)
- 断网重连后数据是否恢复(自动重试还是需手动刷新)
- 重复点击是否产生重复请求(要防重复提交)
- 网络切换(WiFi ↔ 4G)时是否有感知处理
- 数据是否重复下载(重连后是否全量重拉)
⚠️ 经典 Bug:弱网点两次提交按钮 → 产生两笔订单。这是移动端特有的、必须覆盖的测试点。
十一、兼容与安装专项
11.1 兼容性维度
| 维度 | 覆盖重点 |
|---|---|
| Android 版本 | 覆盖近 3~4 个大版本 + 最低支持版本 |
| 厂商 ROM | 小米 MIUI/澎湃、华为 EMUI/HarmonyOS、OPPO ColorOS、vivo OriginOS、三星 OneUI |
| 分辨率/屏幕 | 720p/1080p/2K、刘海屏/挖孔屏、折叠屏、平板 |
| 芯片 | 高通、联发科、麒麟(影响 so 库兼容) |
| 系统设置 | 深色模式、大字体、语言、权限关闭 |
详见 兼容性测试。
11.2 云测平台(解决机型不足)
| 平台 | 特点 |
|---|---|
| Testin 云测 | 国内老牌,真机多 |
| 阿里云移动测试(EMAS) | 机型全,支持脚本 |
| 腾讯优测 / WeTest | 腾讯系,游戏测试强 |
| Firebase Test Lab | Google 官方,海外首选 |
| BrowserStack / SauceLabs | 全球机型,含 iOS |
11.3 安装与升级测试点
| 场景 | 命令/操作 | 验证点 |
|---|---|---|
| 首次安装 | adb install app.apk |
权限弹窗、引导页、无残留数据 |
| 覆盖安装 | adb install -r app.apk ★ |
登录态保留、数据不丢、不闪退 |
| 降级安装 | adb install -d app.apk |
一般应禁止或提示(数据结构不兼容风险) |
| 卸载 | adb uninstall <pkg> |
数据清除干净 |
| 卸载重装 | uninstall -k + 重装 |
验证旧数据兼容(易出 Bug) |
| 安装中断 | 安装中拔线/断电 | 不能产生损坏的半成品 |
| 空间不足 | 填满存储后安装 | 有明确提示,不崩溃 |
十二、中断与其他专项
12.1 中断测试(移动端特有)
| 中断场景 | 验证点 |
|---|---|
| 来电 | 通话中 App 是否正常,挂断后恢复 |
| 短信/通知 | 是否被打断、数据是否丢失 |
| 按 Home 切后台 | 再回来状态是否恢复(Activity 被回收后重建是重灾区) |
| 锁屏/解锁 | 是否需要重新登录、定时器是否正常 |
| 旋转屏幕 | 布局不错乱、数据不丢失(Activity 重建) |
| 低电量/关机 | 数据是否保存 |
| 插拔数据线/耳机 | 是否有监听处理 |
| 分屏/小窗 | 布局适配 |
| 系统权限被关 | 是否有引导,不能崩溃 |
⚠️ 必测经典场景:填了一堆表单 → 切后台 → 系统内存不足杀掉进程 → 再回来。Activity 被回收重建后如果没有
onSaveInstanceState保存,用户输入会全部丢失。
12.2 权限测试
# 授予/撤销运行时权限(无需点弹窗)
adb shell pm grant com.example.app android.permission.CAMERA
adb shell pm revoke com.example.app android.permission.CAMERA
# 查看应用申请的权限
adb shell dumpsys package com.example.app | grep permission
| 测试点 | 验证 |
|---|---|
| 权限被拒绝 | 功能不可用但有提示,不能崩溃 |
| 权限被永久拒绝 | 有引导去设置页 |
| 权限被系统回收 | 重新申请 |
| 最小必要权限 | 不能过度申请(合规要求) |
12.3 安全与隐私
# 检查日志是否泄露敏感信息(测试重点)
adb logcat | grep -iE "password|token|身份证|手机号|cardNo"
# 检查组件是否被导出(越权风险)
adb shell dumpsys package com.example.app | grep -i "activity\|service\|receiver"
# 关注 exported="true" 的组件,非必要导出存在被恶意调用的风险
- 敏感信息:日志、本地存储、SharedPreferences 中不得有明文密码/身份证/银行卡
- 传输安全:全部走 HTTPS(配合 抓包工具 验证)
- 本地存储:数据库/SP 是否加密
12.4 其他专项
| 专项 | 要点 |
|---|---|
| 推送 | 到达率、点击跳转、离线消息、免打扰 |
| 国际化 | 多语言文案、RTL 语言、时间/货币格式、长文案截断 |
| 无障碍 | 内容描述、字体放大不截断、对比度 |
| 发热 | 长时间运行后的温度变化 |
| 后台保活 | 是否被系统清理、清理后能否恢复 |
十三、自动化采集脚本实战
13.1 Python 封装 ADB
# adb_helper.py
import subprocess
import time
import re
def adb(args: list, device: str = None, check: bool = True) -> str:
"""执行 adb 命令"""
cmd = ["adb"]
if device:
cmd += ["-s", device]
cmd += args
result = subprocess.run(cmd, capture_output=True, text=True,
encoding="utf-8", errors="ignore")
if check and result.returncode != 0:
raise RuntimeError(f"adb {' '.join(args)} 失败: {result.stderr}")
return result.stdout.strip()
def devices() -> list:
"""获取已连接且可用的设备列表"""
out = adb(["devices"])
return [line.split("\t")[0]
for line in out.splitlines()[1:]
if "\tdevice" in line]
def force_stop(pkg: str, device: str = None):
adb(["shell", "am", "force-stop", pkg], device)
def start_app(pkg: str, activity: str = None, device: str = None, wait: bool = False):
cmd = ["shell", "am", "start"]
if wait:
cmd.append("-W")
if activity:
cmd += ["-n", f"{pkg}/{activity}"]
else:
cmd += ["-n", f"{pkg}/.MainActivity"]
return adb(cmd, device)
def current_activity(device: str = None) -> str:
"""获取当前前台 Activity"""
out = adb(["shell", "dumpsys", "activity", "activities"], device)
m = re.search(r"mResumedActivity:.*?(\S+)/(\S+)", out)
return f"{m.group(1)}/{m.group(2)}" if m else ""
13.2 启动耗时自动采集
def measure_start_time(pkg: str, activity: str, times: int = 5,
device: str = None) -> dict:
"""冷启动耗时:多次测量取中位数"""
results = []
for i in range(times):
force_stop(pkg, device)
time.sleep(2) # 等待进程完全退出
out = start_app(pkg, activity, device, wait=True)
m = re.search(r"TotalTime:\s*(\d+)", out)
if m:
results.append(int(m.group(1)))
print(f"第 {i+1} 次: {m.group(1)} ms")
results.sort()
return {
"all": results,
"median": results[len(results) // 2], # 中位数(推荐用)
"min": min(results),
"max": max(results),
"avg": sum(results) / len(results),
}
# 使用
if __name__ == "__main__":
r = measure_start_time("com.example.app", ".MainActivity", times=5)
print(f"冷启动中位数: {r['median']} ms")
# 断言(接 pytest)
assert r["median"] < 2000, f"冷启动超时: {r['median']} ms"
13.3 内存监控(泄漏初筛)
def get_pss(pkg: str, device: str = None) -> int:
"""获取应用 PSS 内存(KB)"""
out = adb(["shell", "dumpsys", "meminfo", pkg], device)
m = re.search(r"TOTAL\s+(\d+)", out)
return int(m.group(1)) if m else 0
def get_activity_count(pkg: str, device: str = None) -> int:
"""获取 Activity 实例数(泄漏监控)"""
out = adb(["shell", "dumpsys", "meminfo", pkg], device)
m = re.search(r"Activities:\s*(\d+)", out)
return int(m.group(1)) if m else 0
def memory_leak_check(pkg: str, page_action, rounds: int = 10,
device: str = None, threshold: float = 1.5):
"""反复进出页面,检查内存是否持续增长"""
force_stop(pkg, device)
start_app(pkg, device=device)
time.sleep(3)
base_pss = get_pss(pkg, device)
for i in range(rounds):
page_action() # 外部传入:进入页面→返回
time.sleep(1)
if i % 5 == 0:
print(f"第 {i} 轮 PSS: {get_pss(pkg, device)} KB")
final_pss = get_pss(pkg, device)
print(f"初始 PSS: {base_pss} KB → 最终 PSS: {final_pss} KB")
print(f"Activity 实例数: {get_activity_count(pkg, device)}")
assert final_pss < base_pss * threshold, \
f"疑似内存泄漏,PSS 从 {base_pss} 增长到 {final_pss}"
13.4 崩溃/ANR 自动监听
import threading
def watch_crash(pkg: str, duration: int = 60, device: str = None) -> list:
"""监听一段时间内的崩溃与 ANR"""
adb(["logcat", "-c"], device) # 清空旧日志
issues = []
proc = subprocess.Popen(
["adb"] + (["-s", device] if device else []) + ["logcat", "-v", "threadtime"],
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
text=True, encoding="utf-8", errors="ignore"
)
def reader():
for line in proc.stdout:
if "FATAL EXCEPTION" in line or "AndroidRuntime" in line:
issues.append(("CRASH", line.strip()))
elif "ANR in" in line:
issues.append(("ANR", line.strip()))
t = threading.Thread(target=reader, daemon=True)
t.start()
time.sleep(duration)
proc.terminate()
return issues
# 使用:跑 Monkey 或自动化用例时同步监听
issues = watch_crash("com.example.app", duration=120)
assert not issues, f"检测到稳定性问题: {issues}"
💡 把
watch_crash与 UI 自动化(如 UIAutomator2)结合,跑用例的同时后台监听崩溃,就能实现”功能自动化 + 稳定性监控”二合一。
十四、移动端专项测试流程
1. 明确场景与指标
例:首页冷启动 < 1500ms;列表滑动 Janky < 5%
2. 准备环境
□ 清空日志:adb logcat -c
□ 重置统计:dumpsys gfxinfo reset / batterystats --reset
□ 恢复干净状态:pm clear + force-stop
□ 记录设备信息:型号、系统版本、电量、可用内存
3. 执行测试
□ 手动/脚本执行目标场景
□ 同步采集数据(启动耗时、内存、FPS、流量)
□ 后台监听崩溃与 ANR
4. 数据整理
□ 多次测量取中位数(至少 5 次)
□ 与基线/竞品对比
5. 输出报告
□ 指标表格 + 是否达标
□ 异常数据附 logcat / traces / 录屏
□ 明确复现步骤
十五、常见问题速查
| 问题 | 原因 | 解决 |
|---|---|---|
adb devices 无设备 |
数据线只充电 / 驱动未装 | 换线、装驱动、确认 USB 调试开启 |
状态 unauthorized |
手机未授权 | 手机端点”允许”,重插 |
状态 offline |
adbd 异常 | adb kill-server && adb start-server |
adb install 失败 INSTALL_FAILED_VERSION_DOWNGRADE |
降级安装 | 加 -d 参数或先卸载 |
INSTALL_FAILED_TEST_ONLY |
debug 包 | 加 -t 参数 |
INSTALL_FAILED_INSUFFICIENT_STORAGE |
空间不足 | 清理手机存储 |
adb pull /data/... 权限拒绝 |
非 root 不可访问私有目录 | 用 run-as(需 debug 包)或让开发导出 |
input text 中文乱码 |
不支持中文 | 用 ADBKeyBoard 广播 |
dumpsys meminfo 无数据 |
进程不存在 | 先启动 App |
| 测量数值波动大 | 首次加载/GC/其他进程干扰 | 多测几次取中位数,测前 force-stop |
| logcat 日志刷屏找不到 | 日志太多 | -c 清空 + 按 tag/pid 过滤 |
十六、学习资源
| 资源 | 说明 |
|---|---|
| Android 开发者 - ADB 文档 | 官方权威命令参考 |
| Android 调试桥 (adb) 中文文档 | 中文版 |
| Android 性能专项官方文档 | 渲染、内存、电量官方指南 |
| Battery Historian | 耗电分析工具 |
| Systrace / Perfetto | 系统级性能分析 |
| SoloPi | 支付宝开源的手持式性能测试工具(免 Root) |
| Tencent GT / APM | 腾讯性能测试工具 |
| perfmon / Emmagee | 网易开源性能监控 |
| ADBKeyBoard | 解决 adb 中文输入 |