2024年Android最全Android应用卡顿监控方案原理和对比_android jankstats(1),这些细节在Android面试上要注意了

总结

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?有没有免费资料可以借鉴?为此我整理了一份Android学习资料路线:

这里是一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套BAT大厂面试资料专题包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家。

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

这些只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢再关注一下~

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

下面介绍一下市面上开源方案的几种实现方式和简单对比。

1 Choreographer的FrameCallback

TinyDancer 就是通过这种方式计算出FPS。 核心代码:

Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
    override fun doFrame(frameTimeNanos: Long) {
        if (lastFrameTimeNanos > 0) {
            val frameTime = (frameTimeNanos - lastFrameTimeNanos) / NANOS_PER_MS
        }
        lastFrameTimeNanos = frameTimeNanos
        Choreographer.getInstance().postFrameCallback(this)
    }

})

通过记录doFrame回调的间隔时间作为每帧耗时frameTime,如此也很容易计算出FPS=1000/frameTime,掉帧数=frameTime/16.6。

2 Choreographer + Looper

Tencent/matrix 虽然也是基于Choreographer,但其监测FPS的机制和上面的FrameCallback方案不太一样。 由前面Choreographer的介绍可知,所谓每一帧其实指的就是input、animation、traversal三种事件对应的三个doCallback方法的执行结果,而matrix统计帧耗时就是通过监测这三个方法的执行总时间来表示。matrix监测FPS的主要实现在LooperMonitorUIThreadMoniter两个类里。

  1. LooperMonitor为主线程Looper设置一个Printer来监听UI线程每个Message的开始、结束,从而得到Message的执行耗时。(方案同 BlockCanary
class LooperPrinter implements Printer {
    @Override
    public void println(String x) {
        ......
        dispatch(x.charAt(0) == '>', x);
    }
    
    private void dispatch(boolean isBegin, String log) {
        for (LooperDispatchListener listener : listeners) {
            if (isBegin) {
                listener.onDispatchStart(log);
            } else {
                listener.onDispatchEnd(log);
            }
         }
    }
}

  1. UIThreadMoniter通过java反射向Choreographer的CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL三个事件的callback队列的头部插入自定义callback。
```java
    //UIThreadMonitor.java
    @Override
    public void run() {
        doFrameBegin(token);
        doQueueBegin(CALLBACK_INPUT);

        addFrameCallback(CALLBACK_ANIMATION, new Runnable() {
            @Override
            public void run() {
                doQueueEnd(CALLBACK_INPUT);
                doQueueBegin(CALLBACK_ANIMATION);
            }
        }, true);

        addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {
            @Override
            public void run() {
                doQueueEnd(CALLBACK_ANIMATION);
                doQueueBegin(CALLBACK_TRAVERSAL);
            }
        }, true);
    }


前面提到Choreographer收到VSync信号时,也是往主线程消息队列里放入一个Message最终触发doFrame。而LooperMonitor监控了每个Message执行的开始/结束,如果UIThreadMonitor的doFrameBegin被执行,则说明当前在 Looper 中正在执行的消息就是渲染的消息。然后再在Message结束的时候作为当前帧绘制的结束。这个整个一帧的监控就闭环了。
所以在Matrix中,完整的一帧耗时是onDispatchStart -> doFrame -> onDispatchEnd

因为UIThreadMonitor是在Choreographer的callback队列的头部添加callback,所以可用于记录每种类型callback队列执行的开始时间,在队列里的callback都执行完毕后,就可以计算对应的事件的耗时(CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL),比如CALLBACK_INPUT耗时 = CALLBACK_ANIMATION开始时间 - CALLBACK_INPUT开始时间

所以Matrix不仅可以得到每一帧的耗时,还能进一步得到每一帧内触摸事件、动画、View渲染三种事件的耗时情况。

3 官方方案——JankStats

JankStats —— 是官方推出的Jetpack套件中的一个新库,最早发布于2022年2月,该库提供了在运行时获取界面的每帧性能的回调,可以让开发者监测到性能问题及其发生的原因。
项目中依赖:

dependencies {
    implementation "androidx.metrics:metrics-performance:1.0.0-alpha03"
}

简单使用:

val listener = JankStats.OnFrameListener { frameData ->
    // A real app could do something more interesting, like writing the info to local storage and later on report it.
    Log.v("JankStatsSample", frameData.toString())
}
jankStats = JankStats.createAndTrack(window, listener)
jankStats.isTrackingEnabled = true

简单几行代码就可以在OnFrameListener回调中获取每一帧的性能数据FrameData, 同时JankStats库还提供了PerformanceMetricsState供开发者记录当前的界面状态,并通过FrameData回调出来,有助于了解用户在那一帧期间做了什么交互。

open class FrameData(
    frameStartNanos: Long,//当前帧开始时间
    frameDurationUiNanos: Long,//当前帧耗时
    isJank: Boolean,//是否发生了卡顿
    val states: List<StateInfo>//业务代码记录的UI状态,可以通过PerformanceMetricsState在关键业务代码处埋点
) 

原理

JankStats是一个典型的Android X库:在不同的Android版本和不同的设备上,实现行为一致的框架。

    class JankStats private constructor(window: Window, private val frameListener: OnFrameListener) {
        init {
            val decorView: View? = window.peekDecorView()
            implementation =
                when {
                    Build.VERSION.SDK_INT >= 31 -> {
                        JankStatsApi31Impl(this, decorView, window)
                    }
                    Build.VERSION.SDK_INT >= 26 -> {
                        JankStatsApi26Impl(this, decorView, window)
                    }
                    Build.VERSION.SDK_INT >= 24 -> {
                        JankStatsApi24Impl(this, decorView, window)
                    }
                    Build.VERSION.SDK_INT >= 22 -> {
                        JankStatsApi22Impl(this, decorView)
                    }
                    Build.VERSION.SDK_INT >= 16 -> {
                        JankStatsApi16Impl(this, decorView)
                    }
                    else -> {
                        JankStatsBaseImpl(this)
                    }
                }
        }
    }

阅读源码可以发现监测机制在API 24(7.0)前后有差异:
Android7.0及其以上系统,直接通过 Window 的新方法addOnFrameMetricsAvailableListener,监听回调每一帧的详细数据FrameMetrics:

internal open class JankStatsApi24Impl() {
    @RequiresApi(24)
    private fun Window.getOrCreateFrameMetricsListenerDelegator():
        DelegatingFrameMetricsListener {
        .....
            val delegates = mutableListOf<Window.OnFrameMetricsAvailableListener>()
            delegator = DelegatingFrameMetricsListener(delegates)
            //Window的这个方法可以监听每一帧的详细数据
            addOnFrameMetricsAvailableListener(delegator, frameMetricsHandler)
        }
        return delegator
    }
}

Android7.0以下设备,还是需要基于Choreographer,通过反射 Choreographer 的 mLastFrameTimeNanos 来获取当前帧的起始时间,然后通过 ViewTreeObserver的 OnPreDrawListener来感知绘制的开始,并往主线程的消息队列头部插入runnable来获取当前帧的UI线程绘制任务结束时间。

//JankStatsApi16Impl.kt
internal open class DelegatingOnPreDrawListener() : ViewTreeObserver.OnPreDrawListener {
    override fun onPreDraw(): Boolean {
        val frameStart = getFrameStartTime()
        with(decorView) {
            //往主线程的消息队列头部插入runnable
            handler.sendMessageAtFrontOfQueue(Message.obtain(handler) {
                val now = System.nanoTime()
                val frameTime = now - frameStart//获得每帧绘制的真实耗时
            })
        }
        return true
    }
    
    //反射 Choreographer 的 mLastFrameTimeNanos
    private fun getFrameStartTime(): Long {
        return choreographerLastFrameTimeField.get(choreographer) as Long
    }
}

获取到了当前帧的起始时间和绘制的Message结束时间,则可以计算出每帧绘制的真实耗时frameTime = now - frameStart

方案对比

  1. Choreographer的FrameCallback
    • 优点:
      • 简单可靠,维护成本低,使用稳定性高的系统开放API
    • 缺点:
      • 调用postFrameCallback()会不断地请求Vsync(scheduleVsyncLocked()),当界面静止时,UI线程也会不断请求VSync信号。主线程不干活的时候也会监测,可能会把有卡顿的场景的数据给稀释掉了。
      • 获取到的帧耗时并不是真实的每帧绘制耗时,而获取到≥16ms的两次doFrame的时间间隔。
    • 适用场景:
      • 比较粗粒度的掉帧数、卡顿监控。
  2. Matrix(Choreographer + Looper)
    • 优点:
      • 可以对UI线程绘制的不同阶段耗时进行较细致的监控
      • 不需要postFrameCallback,避免不断请求VSync信号
    • 缺点:
      • 维护成本高,对系统api侵入式较强,大量地通过反射来hook Choreographer和Looper,容易出现兼容问题,高版本系统可能会失效
      • 只统计了Choreographer的callback队列执行的耗时,即UI操作相关的耗时,没有包含两个VSYNC之间产生的其它非UI操作相关的message的耗时,因此统计出来的帧耗时可能偏低。
    • 适用场景:
      • 比较精细化的卡顿监控
【附】相关架构及资料

源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,和技术大牛一起讨论交流解决问题。

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

交流解决问题。**

[外链图片转存中…(img-v6Nuikh5-1715708368251)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值