Android应用性能优化: 应用卡顿、ANR

包含以下几个方面

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")
        }
    }

四. 线上如何监控

这个比较复杂,涉及前后端,看大厂吧。

字节友盟

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值