1.卡顿的定义
如果在一个Vsync周期内(60HZ的屏幕上就是16.6ms),按照整个上帧显示的执行的顺序来看,应用UI线程的绘制、RenderThread线程的渲染、SurfaceFlinger/HWC的图层合成以及最终屏幕上的显示这些动作没有全部都执行完成的话,屏幕上就会显示上一帧画面的内容,也就是掉帧,而人的肉眼就可能会感觉到画面卡顿。
2.卡顿监控定位问题
线下监控工具
-
2.1 BlockCanary: 动态检测消息执行耗时。
基于消息机制,向Looper中设置Printer,监控dispatcher到finish之间的操作,满足耗时阀值dump堆栈、设备信息,以通知形式弹出卡顿信息以供分析。
其中最核心的两步是在调用msg.target.dispatchMessage(msg),进行消息的分发前记录时间T1,调用msg.target.dispatchMessage(msg)进行消息分发后记录时间T2,如果T2-T1大于设置的卡顿阈值就会打印当前方法调用堆栈以及显示其他相关提示或打印日志;
blockcanary充分的利用了Loop的机制,在MainLooper的loop方法中执行dispatchMessage前后都会执行printer的println进行输出,并且提供了方法设置printer。通过分析前后打印的时差与阈值进行比对,从而判定是否卡顿。
创建AppBlockCanaryContext:
class AppBlockCanaryContext : BlockCanaryContext() {
}
在application中初始化blockCanary:
BlockCanary.install(this, AppBlockCanaryContext()).start()
BlockCanary会在发生卡顿(通过MonitorEnv的getConfigBlockThreshold设置)的时候记录各种信息,输出到配置目录下的文件,并弹出消息栏通知(可关闭)。
dump的信息包括:
基本信息:安装包标示、机型、api等级、uid、CPU内核数、进程名、内存、版本号等
耗时信息:实际耗时、主线程时钟耗时、卡顿开始时间和结束时间
CPU信息:时间段内CPU是否忙,时间段内的系统CPU/应用CPU占比,I/O占CPU使用率
堆栈信息:发生卡慢前的最近堆栈,可以用来定位卡慢发生的地方和重现路径
-
2.2 滴滴Dokit接入
帧率检测:帧率信息提供波形图查看功能,让帧率监控的趋势更加明显。
CPU检测:CPU 使用率信息提供波形图查看功能,让 CPU 监控的趋势更加形象。
卡顿检测:我们做App开发时,可能会遇到卡顿的情况,往往会忽略,而且复现时出现卡顿的概率也挺挺小的。而卡顿功能帮助我们把App出现的卡顿记录下来,锁定 App 出现卡顿的时刻,打印出对应的代码调用堆栈。
阿里开源工具Solopi监测实战
使用阿里巴巴公司开源性能测试工具SoloPi。
Github主页地址: https://github.com/alipay/SoloPi
工具下载地址:https://github.com/alipay/SoloPi/releases/tag/v0.12.0
帧率测试标准:
测试内容 | 测试描述 | 通过标准 |
---|---|---|
应用冷启动完成时间 | 记录应用开始启动到进入界面的时间 | 中高端机:1500ms 低端机:3500ms |
界面滑动过程平均刷新帧率 | 测试应用界面内滑动时的平均帧率 | 最高帧为目标帧率(需识别出当前界面最高帧),平均帧与目标帧之差不能超过5%,std<5% |
界面滑动过程连续丢帧数 | 进入被测界面先滑动等界面所有数据加载完成了再测试 | 连续丢帧不能超过3帧 |
CPU占用标准
测试内容 | 测试描述 | 通过标准 |
---|---|---|
测试亮屏情况下后台应用对CPU的占用情况 | 测试应用切换到后台亮屏状态下应用CPU占用 | 统计10分钟的均值不超过3% |
测试灭屏情况下后台应用对CPU的占用情况 | 测试应用切换到后台灭屏状态下应用CPU占用 | 统计10分钟的均值不超过2% |
然后找到我们的目标App,进行如上指标测试。
2.4 Androidstudio Profile 卡顿定位实战
见另一篇文章链接:https://www.jianshu.com/p/7186982ef3a4
2.5 线上监控工具
基调听云APM(应用性能管理)产品能够提供代码级性能监控并对故障快速定位,能够兼容OpenTelemetry框架实现全量采集,支持微服务架构下的应用性能监控和治理。
大致工作流程:
1、首先在客户端(Android、iOS、Web等)采集数据;
2、接着将采集到的数据整理上报到服务器;
3、服务器接收到数据后建模、存储、挖掘分析,让后将数据可视化,供用户使用。
Android APM 的原理其实非常简单,用一句话总结就是:
依据打包原理,在 class 转换为 dex 的过程中,调用 gradle transform api 遍历 class 文件,借助 Javassist、ASM 等框架修改字节码,插入我们自己的代码实现性能数据的统计。以上所有过程都是在编译期完成的。
3.卡顿优化处理方案
- 应用UI线程耗时引起卡顿
例如在页面加载初始化时,View加载上,view点击事件上,adapter的bindViewholder上这些地方,做了UI线程的耗时操作,引起卡顿。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 执行UI操作
TextView textView = findViewById(R.id.textView);
textView.setText("Task completed!");
}
});
优化建议:使用异步任务或线程池来执行耗时操作,以避免阻塞UI线程。比如
在异步任务做,使用协程切子线程操作。
- 频繁的内存分配和释放:
for (int i = 0; i < 1000; i++) {
String result = expensiveOperation();
// 执行其他操作
}
private String expensiveOperation() {
// 模拟耗时操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "result";
}
优化建议:避免在循环中频繁创建和释放对象,尽量复用对象或使用对象池。
// 优化示例:复用对象
private StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 1000; i++) {
stringBuilder.setLength(0); // 清空StringBuilder
expensiveOperation(stringBuilder);
String result = stringBuilder.toString();
// 执行其他操作
}
private void expensiveOperation(StringBuilder stringBuilder) {
// 模拟耗时操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
stringBuilder.append("result");
}
参考:
https://mp.weixin.qq.com/s/ff1l-80htFoU3xoH8O49zg
https://www.jianshu.com/p/03dd61816051
https://mp.weixin.qq.com/s/v618ky870WWr5RqoE5_U3Q
https://zhuanlan.zhihu.com/p/533348005