ADB与移动端专项相关

15 minute read

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

手机端准备:

  1. 设置 → 关于手机 → 连续点击「版本号」7 次 → 开启开发者模式
  2. 设置 → 系统 → 开发者选项 → 打开「USB 调试」
  3. 数据线连接电脑,手机上点「允许 USB 调试」(勾选”始终允许”)
  4. 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 中文输入