包含以下几个方面
1. 卡顿原因分析
2. 开发阶段如何给出性能提示?
3. 测试阶段如何体现卡顿质量?
4. 线上如何监控?
一. 卡顿原因分析
1. 界面绘制
ui布局复杂(层级过多)、 过度绘制;
解决:
1. 借助 Hierarchy Viewer 不仅可以以图形化树状结构的形式展示出UI层级, 还对每个节点给出了三个小圆点, 以指示该元素 Measure, Layout, Draw 的耗时及性能。
2. ”系统设置”–>”开发者选项”–>”调试GPU过度绘制”中开启调试
2. ui线程耗时操作
3. 频繁gc
执行 GC 操作的时候,任何线程的任何操作都会需要暂停,等待 GC 操作完成之后,其他操作才能够继续运行。
参考: 调试 ART 垃圾回收
4. 系统资源紧张,cpu负载高
二. 开发阶段如何给出性能提示
ui线程代码执行方式
1. 主线程Handler
2. activity生命周期
1. Handler
开发约束使用统一自定义的Handler、Runnable类, 要求务必设置有意义名称。
在dispatchMessage中监控主线程方法执行,如果消息延迟或者执行时间超过限制,打印日志或弹出toast, 方便直接定位耗时逻辑代码;
override fun dispatchMessage(msg: Message) {
val debug = AkFeature.isDeveloperMode() && looper == Looper.getMainLooper()
if (debug) {
var dispatchTime: Long = SystemClock.uptimeMillis()
val task = msg.callback
val content = if (task != null) {
if (task is AkRunnable) "task: ${task.name}" else "task: not use AkRunnable"
} else {
"msg: what=${msg.what}"
}
// 执行时间,判断是否延迟过长,发出提示
if (dispatchTime - msg.`when` > DEBUG_MAIN_HANDLER_DISPATCH_TIMEOUT) {
AkLog.t(TAG)
.w("$mName, main handler dispatch timeout $DEBUG_MAIN_HANDLER_DISPATCH_TIMEOUT, $content")
}
val result = super.dispatchMessage(msg)
// 执行时长,判断是否执行耗时过长,发出提示
if (SystemClock.uptimeMillis() - dispatchTime > DEBUG_MAIN_HANDLER_EXECUTE_TIMEOUT) {
AkLog.t(TAG)
.w("$mName, main handler execute timeout $DEBUG_MAIN_HANDLER_EXECUTE_TIMEOUT, $content")
}
return result
} else {
return super.dispatchMessage(msg)
}
}
三. 测试阶段如何体现卡顿质量
监测维度
1. 主线程灵敏度
2. 主线程Handler任务耗时监测
3. 页面渲染耗时
4. activity生命周期耗时监测
1. 主线程灵敏度
参考Android WatchDog机制,起了个单独监测线程,同时向主线程发送一个变量+1的操作,然后休眠一定时间阈值(阈值可自定义,例如500s),休眠过后再判断变量是否已经+1,如果未完成则超时告警。
可以建立多个实例进行主线程监测,设置不同阈值: 100ms、300ms、500ms等,这样可以在测试阶段导出一份卡顿数据, 计算出每个界面不同阈值下灵敏度。
- 比如500ms阈值连续100次轮询,只有1次超时, 那在500ms这个粒度下灵敏度为99%;
- 同时监测线程消息接收比预计的延迟有助于说明系统负载强度;
2. 主线程Handler任务耗时监测
参考上边自定义Handler。测试阶段可将超过阈值的handler执行信息输出到本地日志中,提交开发分析。
3. 页面渲染耗时
注意只在绘制调用时(OnDrawListener)监测。
使用 Choreographer.getInstance().postFrameCallback 监测帧绘制时间。
window.decorView.viewTreeObserver.addOnDrawListener {
mStartDrawTime = System.nanoTime()
Choreographer.getInstance().postFrameCallback(this)
}
override fun doFrame(frameTimeNanos: Long) {
// 计算绘制耗时,如果超过阈值输出到质量日志
val millis: Long = (frameTimeNanos - mStartDrawTime) / 1000000
}
4. activity生命周期耗时监测
通过Hook Instrumentation实现生命周期耗时监测
// 关键位置代码
// 如何hook
private fun hookInstrumentation() {
val activityThreadCls = Class.forName("android.app.ActivityThread")
val currentActivityThread = activityThreadCls.getDeclaredMethod("currentActivityThread")
var acc = currentActivityThread.isAccessible
if (!acc) {
currentActivityThread.isAccessible = true
}
// 获取主线程对象
val activityThreadObject = currentActivityThread.invoke(null)
if (!acc) {
currentActivityThread.isAccessible = acc
}
//获取Instrumentation字段
val instrumentationField: Field = activityThreadCls.getDeclaredField("mInstrumentation")
acc = instrumentationField.isAccessible
if (!acc) {
instrumentationField.isAccessible = true
}
// 把系统的instrumentation替换为自己的Instrumentation对象
val curInstrumentation = instrumentationField.get(activityThreadObject) as Instrumentation
val newInstrumentation = ApmInstrumentation(curInstrumentation)
instrumentationField.set(activityThreadObject, newInstrumentation)
if (!acc) {
instrumentationField.isAccessible = acc
}
}
// 自定义Instrumentation如何监测耗时
override fun callActivityOnCreate(activity: Activity, icicle: Bundle?) {
val start = SystemClock.uptimeMillis()
mDelegate.callActivityOnCreate(activity, icicle)
val cost = SystemClock.uptimeMillis() - start
// 判断耗时是否超过自定义阈值
if (cost > 100) {
AkLog.t(TAG).w("${activity.componentName.className}, onCreate, cost $cost")
}
}
四. 线上如何监控
这个比较复杂,涉及前后端,看大厂吧。