Android性能优化

Android性能优化

一、卡顿优化

前言:说到卡顿我们可能正常能想到是FPS刷新率,这是一个平均值,FPS高并不代表页面流畅,比如一个页面某一贞耗时了160毫秒,但是其他都是16毫秒,那么这个页面通过FPS的数据来看体现不出来卡顿,但是实际是用户明显的感觉到了卡顿的感觉。
那么我们可以通过卡顿的帧数跟总帧数的占比来判定页面卡顿情况

卡顿率 = 卡顿的帧数 / 总帧数

加入屏幕数心率是60/s,那么每帧耗时16ms,如果有的帧数超过了16毫秒就发生了掉帧,也就是卡顿。比如某一贞耗时160毫秒,那么我们就认为掉了9帧,根据掉帧的数量可以分级为下面的情况
在这里插入图片描述
1、获取各帧耗时一般有以下两种方案

  1. 通过设置自定义android.util.Printer,监听Looper的dispatchMessage耗时
  2. 通过向Choreographer循环注册FrameCallback,统计两次Vsync事件时间间隔
  3. 谷歌提供的AndroidX系列的组件JankStats 在Android7以上实现
class JankLoggingActivity : AppCompatActivity() {

    private lateinit var jankStats: JankStats

    private val jankFrameListener = JankStats.OnFrameListener { frameData ->
            // 在实际使用中可以将日志上传到远端统计
            Log.v("JankStatsSample", frameData.toString())
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
            // 初始化 JankStats,传入 window 和卡顿回调
            jankStats = JankStats.createAndTrack(window, jankFrameListener).apply {
            // 支持设置卡顿阈值,默认为2
            this.jankHeuristicMultiplier = 3f
        }

        // 设置页面状态
        val metricsStateHolder = PerformanceMetricsState.getHolderForHierarchy(binding.root)
        metricsStateHolder.state?.putState("Activity", javaClass.simpleName)
        // ...
    }

    override fun onResume() {
     super.onResume()
     // onResume后重新开始统计
     jankStats.isTrackingEnabled = true
    }

    override fun onPause() {
     super.onPause()
     // onPause后停止统计
     jankStats.isTrackingEnabled = false
    }

这里我们可以先将卡顿数据存储在内存或者本地存储中,当卡顿数量达到一定程度或者页面切换时,再统一上传卡顿数据,减少上传次数,如下所示:

internal class JankActivityLifecycleCallback : ActivityLifecycleCallbacks {
    private val jankAggregatorMap = hashMapOf<String, JankStatsAggregator>()
    // 聚合回调
    private val jankReportListener = JankStatsAggregator.OnJankReportListener { reason, totalFrames, jankFrameData ->
            jankFrameData.forEach { frameData ->
             // 获取当前 Activity name
             Log.v("Activity",frameData.states.firstOrNull { it.key == "Activity" }?.value ?: "")
             // 获取掉帧数
                val dropFrameCount = frameData.frameDurationUiNanos / singleFrameNanosDuration
                if (dropFrameCount <= JankMonitor.SLIGHT_JANK_MULTIPIER) {
                    slightJankCount++
                } else if (dropFrameCount <= JankMonitor.MIDDLE_JANK_MULTIPIER) {
                    middleJankCount++
                } else if (dropFrameCount <= JankMonitor.CRITICAL_JANK_MULTIPIER) {
                    criticalJankCount++
                } else {
                    frozenJankCount++
                }
            }
            // 实际使用中可以上传到远端统计
            Log.v("JankMonitor","*** Jank Report ($reason), " +
                        "totalFrames = $totalFrames, " +  // 总帧数
                        "jankFrames = ${jankFrameData.size}, " + // 总卡顿数
                        "slightJankCount = $slightJankCount, " + // 轻微卡顿数
                        "middleJankCount = $middleJankCount, " + // 中等卡顿数
                        "criticalJankCount = $criticalJankCount, " + // 严重卡顿数
                        "frozenJankCount = $frozenJankCount" // 冻结帧数
            )
        }
    }

    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
        // 为所有 Activity 添加卡顿监听
        activity.window.callback = object : WindowCallbackWrapper(activity.window.callback) {
            override fun onContentChanged() {
                val activityName = activity.javaClass.simpleName
                if (!jankAggregatorMap.containsKey(activityName)) {
                    val jankAggregator = JankStatsAggregator(activity.window, jankReportListener)
                    PerformanceMetricsState.getHolderForHierarchy(activity.window.decorView).state?.putState("Activity", activityName)
                    jankAggregatorMap[activityName] = jankAggregator
                }
            }
        }
    }

    // ...
}

如上所示,主要做了以下事:
为所有 Activity 添加了聚合的卡顿监听,当卡顿数达到阈值或者 Activity 退到后台时会触发聚合回调。
在合回调中可以获取这段时间的总帖数,与卡顿的帧的列表,通过计算卡顿帧的掉帧数,我们可以获取总卡顿数,轻微卡顿数,严重卡顿数等。将这些数据上传就可以计算出页面的卡顿率。
在回调中我们同样可以获取页面的状态,比如我们这里设置的activityName,通过设置状态我们可以统计不同场景下的卡顿率,比如滚动与非滚动。
2.如何定位卡顿问题

  1. 堆栈抓取方案:思路其实很简单,在卡顿发生时 Dump 主线程堆栈,通过分析堆栈找到卡顿的原因
  2. 字节码插桩方案:堆栈抓取方案的最大缺陷是无法获取方法的执行耗时,而字节码插桩方式可以完美解决这一问题。Matrix 插桩对于好机器的性能影响可忽略,对差机器性能稍有损耗,但影响很小。对安装包大小影响,对于微信这种大体量的应用,实际插桩函数 16w+,对安装包增加了 800K 左右。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值