客户端专项测试
App专项测试
客户端专项测试主要测什么
- 用户维度
- 崩溃(crash,弱网)
- 卡顿(掉帧,gc, cpu)
- 响应慢(启动时间,交互响应,H5加载)
- 发热(CPU,mem,io,network, gps等硬件使用)
-耗电快(硬件占用) - 兼容性问题(机型覆盖,回归)
- 技术维度
-崩溃
- 自动遍历、monkey测试,横竖屏切换,快速进退- 卡顿(掉帧,gc, cpu)
-卡顿测试,内存泄漏测试,method profile
-响应慢(启动时间,交互响应,h5加载)- 冷热启动、界面切换、h5性能
- 发热(CPU、mem、io、network、gps等硬件使用)
- method profile,gc统计,io统计,流量统计,硬件使用统计,耗电量分析
- 兼容性问题(机型覆盖,回归)
- 兼容性测试,自动化测试,自动遍历测试,monkey测试
- 卡顿(掉帧,gc, cpu)
APP启动性能分析
-
Activity启动流程
- 主要流程
-Application OnCreate
- 加载第三方sdk- Activity OnCreate
- 加载自身的逻辑
- 发送远端数据请求 xxx.json
- 渲染界面 List
- Activity OnCreate
- App启动性能指标
- 冷启动
- 暖启动
- 热启动
- 首屏启动
- 建议时间
- Cold strtup takes 5 seconds or longer
- Warm stsrts takes 2 seconds or longer
- Hot starts takes 1.5 seconds or longer
- 主要流程
-
主要流程
- adb logcat
- 录屏+ 视频拆帧
- uiautomator等自动化工具200ms巡检界面变化
- tranceview
- 硬埋点
-- - 使用adb logcat
- package= com.xueqiu.android
- 清理缓存数据:adb shell pm clear $package
- 停止进程:adb shell am force-stop $ package
- 启动app: adb shell am start -S -W $paackage/.view.WelcomeActivityAlias
- 获取数据: adb logcat |grep -i displayed
- adb logcat结果
- startTime :记录刚准备调用startActivityAmdWait()的时间点;
- endTime:记录startActivityAndWait()函数调用返回的时间点;
- WaitTime: startActivityAndWait()调用耗时;
- WaitTime = endTime - startTime
--
- 使用ffmpeg拆针
- adb shell am force-stop $package
- adb shell screenrecord --bugreport --time-limit 30
- adb shell screenrecord --bugreport --time-limit 30
- adb shell am start -S -W $pckage/.view.WelcomeActivityAlias
- wait
- adb pull / data/local/tmp.xueqiu.mp4.
- ffmpeg -i xueqiu.mp4 xueqiu.gif
- ffmpeg -i xueqiu.mp4 -r 10 frames_%03d.jpg
-
接口性能
- 接口性能常用的工具
- 代理工具:Charles burpsuit
- 抓包工具:tcpdump wireshark
- 接口测试过程
- 接口性能常用的工具
-
Webview性能分析
- Chrome自带的分析工具(62)
- Disable cache:不加载缓存,从零载入
- 蓝线:dom出现
- 红线: 图片等资源已经加载完
- 信息指标
- Queueing :队列等待时间
- Stalled:在队列中,停止请求
- Waiting :服务等待时间
- Content download: 下载时间
- webView在真机中需要打开webView调试
- Chrome自带的分析工具(62)
-
H5性能分析
- https://www.w3.org/TR/navigation-timing/
- 资源加载指标
- prompt for unload: 访问一个新的页面时,旧页面卸载完成的时间
- redirect:重定向,用户注销登录时返回主页面和跳转到其它网站等
- app cache:检查缓存,是否打开
- DNS(域名系统):DNS查询的时间,如果是长连接或者请求文件来自缓存等本地存储则返回fetchStart时间点
- TCP :与服务器建立连接的时间
- request:浏览器发起请求的时间
- response: 拿到第一个响应字节到最后一个响应字节的时间
- processing:各种状态的时间点
- load:触发load事件执行的时间
- 自动化获取性能指标
- appium/selenium的ExecuteScript APi
- 注入js
- return JSON.stringify(windows.performance.timing)
- JSON.stringify(window.performance.timing)
- JSON.stringify(window.performance.getEntriesByName)(document.querySelector("img").src[0],null,2)
# w3 规定的“埋点操作” class TestData: def testData(self): driver = webdriver.Chrome() driver.get("https://baidu.com") print(driver.excute_script("return JSON.stringify(window.performance.timing)"))
- 注入js
- appium/selenium的ExecuteScript APi
-
卡顿分析
- systrace
- 这个图是怎么绘制的?
- sdk/platform-tools/systrace (下载的AndroidStudio自带SDK:位置C:\Users\stormweb\AppData\Local\Android\Sdk\platform-tools\systrace)
- 需要python 2.7
- no module win32con
- pip install pypiwin32
-no six - pip2 install six
- pip install pypiwin32
- 使用
- 启动设备
- 输入命令与参数
- python systrance -e 192.168.81.102:5555-1
- 不加参数启动
- 在命令行:python systrance.py -e 192.168.181.102:5555
- 在设备上进行操作
- 在命令行:按下enter
- 输入命令与参数
- 详细信息
- 帧点
- 绿:16.6ms内,黄,红超过16.6ms
- 任务状态
- 灰:休眠,蓝色:可运行,绿色:运行,橙色:不响应信号
- 函数调用
- 帧点
- 启动设备
- 卡顿影响因素
- 内存问题(内存抖动,full gc)
- CPU耗时
- render(布局复杂,overdraw)
- 帧分析
- 冰冻帧:一个帧超过0.7s
- 帧分析: adb -s devicesname shell dumpsys gfxinfo |less
- systrace
-
CPU的统计
- CPU与GPU的关系
- 图形CPU不允许直接与GPU通信
- 通过中间层来连接这两部分
- 中间件维护一个队列
- CPU把display list放入队列
- GPU从队列中拉取数据进行绘制
- GPU 渲染工具
- Android开发者工具提供性能调优工具
- GPU渲染分析:GPU-RENDRING-PROFILE
- https://developer.android.google.cn/topic/performance/rendering/inspect-gpu-rendering?hl=zh_cn
- GPR显示内容
- 绘制每一帧所消耗的时间
- 不同的颜色代表UI绘制的不同阶段
- 并且在柱形图的中间还有一根绿色的横线代表16ms的绘制时间基准
- GRP 会统计并显示app最近运行的128帧
- 蓝色
- View需要先转换为GPU能识别的格式
- 蓝色较高
- View突然无效(invalidate)
- onDraw函数做了复杂的绘制逻辑
- 红色
- OpenGL处理DISPLAYLIST,将处理的结果传递给GPU
- 红色较高
- View过于复杂
- view重复提交
- 橙色
- CPU在等待GPU完成工作
-橙色较高- GPU任务太多,复杂的view绘制
- CPU在等待GPU完成工作
- Android开发者工具提供性能调优工具
- CPU与GPU的关系
-
mem 统计
- 内存耗用名词解析
- VSS Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
- RSS Resident Set Size Resident Set Size实际使用物理内存(包含共享内存占用的内存)
- PSS Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
- USS Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
- 内存占用规律
- procastasts
- adb shell dumpsys prostats --hours 3
- 输出格式
- 进程名/USER/VersionCode
- 状态:(minPSS-avgPSS-maxPSS/minPSS-avgUSS-maxUSS over samples)
- 输入字段解释
- 百分比:表示在总的时间内,进程在各种状态下的消耗
- TOTAL:表示了进程的综合占用情况
- Img Fg: 加载到前台
- Service:标识了是否是服务
- Persistent: 标识了是否一直驻留在内存中,与Service一样,表示内存进驻的级别
- Top: 标识了是否是顶层进程
- Receiver:标识了是否是广播进程
- meminfo内容
- 做一个版本对比来测内存
- 内存耗用名词解析
-
网络流量分析
- 显示网络流量
- adb shell dumpsys netstats
- 分块展示
- Activity interfaces :活动接口
- Activity UID interfaces :活动UID 接口
- Dev statistics: Xt 统计信息
- UID statistics :UID 统计信息
- UID tag statistics: UID 代码统计信息
- 活动接口和活动UID接口
- Activity interfaces:
- iface = wlan() ident = [{type=WIFI, subType = COMBINED, networkId ="GoogleGuest"}]
- Activity interfaces:
- 找到UID
- adb shell dumpsys package com.xueqiu.android |grep userId
- userId = 10007 gids = [3003, 1028, 1015]
- adb shell dumpsys package com.xueqiu.android |grep userId
- 查看相关应用的流量情况
- set = DEFAULT 表示前台网络使用情况
- set = BACKGROUND 表示后台使用情况
- set = ALL 表示上述两类使用情况
- tag = 0x0 表示与流量关联的套接字代码
- rxBytes 和rxPackets 表示在响应时间间隔内接受的字节数和数据包
- txBytes和txPacksts 在响应时间间隔内发送的字节数和数据包数
- 显示网络流量
-
耗电量测试
- 待机时间成关注指标
- 提升用户体验
- 通过不同的测试场景,找出APP高耗电的场景并解决
- 安装工具
- https://github.com/google/battery-historian
- 注意编译版本 改为20190513
- cd battery-historian
- go get -d -u github.com/google/battery-historian/..
- go run setup.go
- go run cmd/battery-historian/battery-historian.go
- 测试步骤
- 使用batterystats 生成数据
- 使用Battery historian 分析数据
- batteryststs收集数据
- 清理耗电量数据
- adb shell dumpsys batterystats batterystats --reset
- adb shell dumpsys batterystats -- enable full-wake-history
- 运行测试用例/手工操作
- 收集数据
- Android 7.0 :adb bugreport bugreport.zip
- Android 6.0 : adb bugreport > bugreport.txt
- 清理耗电量数据
- historian
- 进入historian
- x轴代表时间周期,默认以60s为一个周期
- 指标含义
- battery_level:电量
- plugged: 充电状态及充电的时长
- screen :屏幕是否被点亮
- top:显示当前手机是运行APP
- status: 电池信息,有充电,放电,未充电,已充满,未知等不同状态。
-
弱网测试
- 封闭环境,网速降低
- 丢包
- 数据无法加载
- 消息更新不及时
- 弱网速度
- 低于2G
- 3G
- 弱网模拟
- Charles
- 字段解释
- Bandwidth
- 理论网速上限
- Utilisation(利用)
- 总带宽的百分比
- Round-trip Latency(请求往返延迟)
- 客户端与服务器第一次往返通信延迟,单位毫秒
- MTU(最大传输单元)
- 传输的TCP数据包的最大尺寸
- Reliability(可靠性)
- 衡量连接完全失败的可能性
- 常用网速的展示
- Bandwidth
- 字段解释
- Charles
- 封闭环境,网速降低
-
健壮性测试
- 用于测试系统出现故障时,是否能够自动恢复或者忽略故障继续运行
- 操作过程
- 对应用进行盲点
- 网络不佳
- 数据不通
- 使用工具
- Monkey, Maxim
- Charles
- Appcrawler
-
兼容性测试
- 几个硬件之间,软件之间,或者是软硬件之间的相互配合程度
- APP兼容性测试
- 移动设备型号多样
- 测试APP在主流设备上能否正常运行
- 测试APP在主流设备上崩溃卡顿现象
- 兼容性测试作用
- 进一步提高产品质量,提高用户体验
- 尽可能达到平台无关性
- 保证软件存在价值,是衡量软件质量的重要指标
- 是产品的市场更广阔
- 测试方法
- 手工测试
- 借助第三方工具
- appcrawler
Android 功耗测试
功耗相关硬件
电池
- 电池容量:电池容量越大手机越笨重
- 充电时间:低电压高电流,高电压低电流
- 寿命
- 安全性
耗电硬件
- 处理器芯片:CPU,GPU,NPU
- 基带芯片:蜂窝网,WIFI,NFC
- 传感器:加速度传感器陀螺仪,气压计,温度传感器
- 外设:相机,麦克风,扬声器
- 其他:GPS,内存,显示屏,闪存
资源调度机制是厂商功耗优化的最重要的手段,手机基带,GPS,这些模块在不使用时也会进入低功耗或休眠模式,达到降低功耗的目的。
手机厂商为了保证头部应用能有更好的体验,厂商愿意给他们分配更多的资源
https://developer.qualcomm.com/software/snapdragon-power-optimization-sdk/quick-start-guide
耗电软件
如何评估软件的耗电情况: 电能 = 电压 * 电流 * 时间
如何监测软件耗电:手机的电压一般恒定,Adnroid 系统要求可以在/frameworks/base/core/res/xml/power_profile.xml找到电源配置文件
如何在不同厂商中获取耗电流
- 从手机中导出 /system/framework/framework-res.apk 文件
- 使用反编译工具对导出文件framework-res.apk 进行反编译
- 查看power_profile.xml 文件在frame-res 反编译目录路径: /res/xml/power_profile.xml
https://source.android.com/devices/tech/power
https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/xml/power_profile.xml
如何监测系统的电量消耗:可以通过dumpsys batterystatus 导出
adb shell dumpsys batterystats > battery.txt
// 各个Uid的总耗电量,而且是粗略的电量计算估计。
Estimated power use (mAh):
Capacity: 3450, Computed drain: 501, actual drain: 552-587
...
Idle: 41.8
Uid 0: 135 ( cpu=103 wake=31.5 wifi=0.346 )
Uid u0a208: 17.8 ( cpu=17.7 wake=0.00460 wifi=0.0901 )
Uid u0a65: 17.5 ( cpu=12.7 wake=4.11 wifi=0.436 gps=0.309 )
...
// reset电量统计
adb shell dumpsys batterystats --reset
编号 | 测试方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|---|
1 | 稳压电源+ 电流仪 | 整机电流 | 以测试整机电流,而且数据精确 | 需要准确硬件工具,测试操作复杂,而且不能准确测试APP消耗电量 |
2 | dumpsys batterystats | App电量 | 有耗电量的详细数据 | 结果可读行比较差 |
3 | 系统“耗电排行” | APP电量 | 直观,跟用户看到的一致 | 没有详细的数据 |
4 | Battery History | App电量 | 结果直观,有耗电量的详细数据 | 适用与Android 5.0及以上系统 |
//7.0和7.0以后
$ adb bugreport bugreport.zip
//6.0和6.0之前:
$ adb bugreport > bugreport.txt
//通过historian图形化展示结果
python historian.py -a bugreport.txt > battery.html
Android 耗电优化历史
野蛮生长: Pre Android 5.0
特点: 电量问题排查复杂。无论是电量的测量,还是耗电问题的排查都异常艰难。
- 耗电与安装应用程序的数量有关,用户安装越多的应用程序,无论是否打开它们,手机耗电都会很快。
- App耗电量与APP使用时间无关,用户希望 App 的耗电量应该与它的使用时间相关,但是有些应用即使常年不打开,依然非常耗电
逐步收紧:Android 5.0 ~ Android 8.0
https://developer.android.com/about/versions/android-5.0?hl=zh-cn
Android 6.0 开始~, Google开始着手清理后台应用和广播来进一步省电
特点:
- 省电模式不够省电
- 用户对应用控制力度不够
3。 Target API 开发者响应不积极
最严格:Android 9.0
耗电优化与线上监控
耗电优化
- 后台耗电(厂商预装项目要求最严格的正是应用后台待机耗电)
- 用户认为应用的耗电量应该是和使用时间有关的
- Camera, Audio, Video,Bluetooth, Network, Sensor. Radio, Screen,WiFi, CPU, GPS
- 符合系统规范,让系统认为你耗电是正常的
- Android Vitals
耗电优化难点
- Android Vitals
- 缺乏现场,无法复现
- 信息不全,难以定位
- 无法评估结果
耗电优化的方法
- 常见的耗电方式
- 某个需求场景
- 代码的Bug
- 优化方法
- 利用厂商通道或定时拉去最新消息
耗电监控
- 利用厂商通道或定时拉去最新消息
- Android Vistals
- 耗电监控应该监控什么
- 监控信息---> 系统关心什么,我们就监控什么(后台耗电监控 ) Alarm wakeup、WakeLock、WiFi scans、Network
- 现场信息---> 完整的对栈信息
- 提炼规则---->将监控的内容抽象成规则
常用的规范
|APP活动|前台规则|后台规则|
| ------------ | ------------ | ------------ |
|Alarm|单个Alarm每小时不能启动超过20次|单个Alarm 每小时不能启动超过10次|
|WakeLock|单个WakeLock每小时不能超过30分钟或者超过20次|1. WakeLock每 小时不能超过30分钟; 2.单个WakeLock每小时不能超过10分钟或12次 |
|Sensor|不监控前台sensor使用|每小时使用不能超过30分钟|
|WIFI scans|每小时扫描不允许超过10次|每小时扫描不允许超过4次|
|Bluetooth scans|每小时扫描不能超过10次|每小时扫描不允许超过4次|
|GPS|每小时使用不能超过30分钟|每小时使用不能超过15分钟|
|Camera|不监控前台相机使用|后台不允许使用相机|
|Network|不监控前台网络耗电|每小时后台网络数据量不能超过10MB|
|CPU|不监控前台CPU|后台CPU持续超过30分钟|
如何监控耗电
Java Hook
WakeLock
1. https://androidxref.com/7.0.0_r1/xref/frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
2. https://developer.android.com/training/scheduling/wakelock
// 代理PowerManagerService
ProxyHook().proxyHook(context.getSystemService(Context.POWER_SERVICE), "mService", this);
@Override
public void beforeInvoke(Method method, Object[] args) {
// 申请Wakelock
if (method.getName().equals("acquireWakeLock")) {
if (isAppBackground()) {
// 应用后台逻辑,获取应用堆栈等等
} else {
// 应用前台逻辑,获取应用堆栈等等
}
// 释放Wakelock
} else if (method.getName().equals("releaseWakeLock")) {
// 释放的逻辑
}
}
**Alarm **: 用来执行定时的重复任务
- https://developer.android.com/training/scheduling/alarms
- https://developer.android.com/reference/android/app/AlarmManager.html#ELAPSED_REALTIME_WAKEUP
- https://developer.android.com/reference/android/app/AlarmManager.html#RTC_WAKEUP
// 代理AlarmManagerService
new ProxyHook().proxyHook(context.getSystemService
(Context.ALARM_SERVICE), "mService", this);
public void beforeInvoke(Method method, Object[] args) {
// 设置Alarm
if (method.getName().equals("set")) {
// 不同版本参数类型的适配,获取应用堆栈等等
// 清除Alarm
} else if (method.getName().equals("remove")) {
// 清除的逻辑
}
}
后台CPU
对于 GPS 监控,我们可以通过 Hook 代理LOCATION_SERVICE。对于 Sensor,我们通过 Hook SENSOR_SERVICE中的“mSensorListeners”
通过 Hook,我们可以在申请资源的时候将堆栈信息保存起来。当我们触发某个规则上报问题的时候,可以将收集到的堆栈信息、电池是否充电、CPU 信息、应用前后台时间等辅助信息也一起带上
功耗测试通常看的是系统整体情况:https://xie.infoq.cn/article/25fa818c88883056750982269