Android 性能监控框架之帧率监控 您所在的位置:网站首页 animate怎么看到上一帧 Android 性能监控框架之帧率监控

Android 性能监控框架之帧率监控

2023-04-08 18:21| 来源: 网络整理| 查看: 265

作者:Drummor1 什么是帧率帧率(Frame rate)是以帧称为单位的 位图 图像连续出现在显示器上的频率(速率)。2 Android 中帧率的监控

线下开发我们可以使用开发者选项的帧率监控或者 adb shell dumpsys gfxinfo packagename进行监控针对性优化。这些方案不能带到线上。

3 简单监控帧率方案

利用Choreographer的postcallback方法接口轮询方式,能够对帧率进行统计。

choreographer.postCallback()内部是挂载了一个CALLBACK_ANIMATION类型的callback。轮训方式往choreographer内添加callback,相邻两个callback执行时间间隔即能粗略统计单帧的耗时。严谨的讲这不是单帧的耗时而是两个【半帧】拼凑的耗时。

代码示例如下。

class PoorFrameTracker { private var mLastFrameTime = -1L private var mFrameCount: Int = 0 val calRate = 200 //ms fun startTrack() { mLastFrameTime = 0L mFrameCount = 0 Choreographer.getInstance().postFrameCallback(object : FrameCallback { override fun doFrame(frameTimeNanos: Long) { if (mLastFrameTime == -1L) { mLastFrameTime = frameTimeNanos } val diff = (frameTimeNanos - mLastFrameTime) / 1_000_000.0f if (diff > calRate) { var fps = mFrameCount / diff * 1000 if (fps > 60) {fps = 60.0f} //todo :统计 mFrameCount = 0 mLastFrameTime = -1 } else { mFrameCount++ } Choreographer.getInstance().postFrameCallback(this); } }) } }优点简单快捷,无黑科技缺点无活动时,也会监控,无效信息会把帧率较低时给平均掉。对应用带来不必要的负担。4 帧率监控进化之一 hook Choreographer

针对章节三的方案,首先我们有两个主要的优化方向希望在主线程不活动的时候不进行帧率的检测

我们调用公开api Choreographer.postCallback()时会触发垂直同步(这部分可以参考另一篇文章)。

# choreographer private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { private long mTimestampNanos; @Override public void onVsync(long timestampNanos, long physicalDisplayId, int frame, VsyncEventData vsyncEventData) { ... mTimestampNanos = timestampNanos; Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); ... } @Override public void run() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame, mLastVsyncEventData); } }【采集每帧的开始】利用Looper中Printer采集Message的开始和结束。上段代码是Choreographer中的一段代码。当收到底层垂直同步信号的时,利用Handler机制post的一个Runable,执行该帧的动作doFrame()。依次我们可以采集到每帧的开始和结束。# Choreographer private final CallbackQueue[] mCallbackQueues;【过滤出每帧的执行动作】我们知道主线程中不单单执行每帧的动作,还会执行其他动作。如何过滤出执行的是每帧的动作。反射往Choreographer往里添加callback不触发垂直同步,同时在同步信号回调时,会调用我们传入的callback,如果执行了传入的callbacl就可以标识该次执行动作是帧的执行动作。【采集真实的垂直同步到达时间】反射拿到mTimestampNanos结合以上,我们能够采集到每帧执行耗时,依次可以计算出准确的帧率。且比我们第一种方案要优雅很多。void doFrame(long frameTimeNanos, int frame, DisplayEventReceiver.VsyncEventData vsyncEventData) { ... final long frameIntervalNanos = vsyncEventData.frameInterval; doCallbacks(Choreographer.CALLBACK_INPUT, frameData, frameIntervalNanos); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameData, frameIntervalNanos); doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameData, frameIntervalNanos); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameData, frameIntervalNanos); doCallbacks(Choreographer.CALLBACK_COMMIT, frameData, frameIntervalNanos); ... }同时我们还可以通过反射的方式给Chorographer 里 mCallbackQueues添加不同的类型动作,采集不同类型动作的耗时。

补充

严格意义上,该方案统计的也不是真实的帧率,而是一帧所有耗时中在UI Thread执行部分的耗时,上图doFrame部分。其他线程和进程还会执行其他动作最终才能完成一帧的绘制。但对于我们应用层来说更关注监控doFrame,我们在应用开发层面大部分能够干预的也在doFrame这部分。5 帧率监控进化之二 滑动帧率#View protected void onScrollChanged(int l, int t, int oldl, int oldt) { ... final AttachInfo ai = mAttachInfo; if (ai != null) { ai.mViewScrollChanged = true; } ... }View里如果有滑动行为产生最终都会调用到onScrollChanged(),当该方法调用的时候,会将mAttachInfo的mViewScrollChanged值设为true#ViewRootImpl private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) { ... if (mAttachInfo.mViewScrollChanged) { mAttachInfo.mViewScrollChanged = false; mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); } }如上代码ViewRootImpl的draw方法会如果check到mAttachInfo.mViewScrollChanged值为true就会就会调用ViewTreeObserver的dispatchOnScrollChanged()方法,只要我们在viewTreeObserver设置监听,就能获取到界面是否正在滑动这一重要事件。整个过程的如上图所示,我们收到滑动回调这一事件的时候,其实是choreographer的doFrame()调用而来。结合上面我们就可以在收到【滑动事件】的时候使用Choreographer的postCallback开始统计帧率。什么时候结束呢?在没有【滑动信息】生成出来的时候看下面代码private var isScroll = false init { window.decorView.viewTreeObserver.addOnScrollChangedListener { //标识正在滑动 isScroll = true //开始统计帧率 Choreographer.getInstance().postFrameCallback(FrameCallback()) } } ​ private inner class FrameCallback : Choreographer.FrameCallback { override fun doFrame(frameTimeNanos: Long) { if (isScroll) { isScroll = false //重置滑动状态 if (lastFrameTime != 0L) { val dropFrame = (((frameTimeNanos - lastFrameTime) / 1000000f / 16.6667f) + 1f).toInt() notifyListener(dropFrame) } lastFrameTime = frameTimeNanos } else { lastFrameTime = 0 } } }

这样我们就实现了一个监控滑动帧率的方案

6 帧率监控进化 之三 官方方案

官方出手,官方在Android N 以上新增了Window.OnFrameMetricsAvailableListener可以监听每帧的执行状态。包含总耗时,绘制耗时,布局耗时,动画耗时,测量耗时。依次我们可以计算出帧率。

private val metricsAvailableListener = Window.OnFrameMetricsAvailableListener { window, frameMetrics, dropCountSinceLastInvocation -> val intent = frameMetrics?.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP) ?: 0 val vsync = frameMetrics?.getMetric(FrameMetrics.VSYNC_TIMESTAMP) ?: 0 val animation = frameMetrics?.getMetric(FrameMetrics.ANIMATION_DURATION) ?: 0 val vsyncTotal = frameMetrics?.getMetric(FrameMetrics.TOTAL_DURATION) ?: 0 val measureCost = frameMetrics?.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) ?: 0 //计算帧率 } ​ this.window.addOnFrameMetricsAvailableListener(//向window注册监听 metricsAvailableListener, Handler(handlerThread.looper)

同时配合Jetpack的FrameMetricsAggregator的可以统计出帧耗时情况。

private val frameMetricsAggregator = FrameMetricsAggregator() frameMetricsAggregator.add(this@FrameActivity) frameMetricsAggregator.metrics?.let { it[FrameMetricsAggregator.TOTAL_INDEX] //总耗时概况 it[FrameMetricsAggregator.INPUT_INDEX] //输入事件耗时 it[FrameMetricsAggregator.DRAW_INDEX] //绘制事件耗时概况 }

FrameMetricsAggregator内部存储比较有意思,是有一个SparseIntArray数组SparseIntArray[] mMetrics = new SparseIntArray[LAST_INDEX + 1],存储各个阶段的耗时SparseIntArray的key为耗时,value为该耗时的个数。

mMetrics[TOTAL_INDEX]: {3=8, 4=13, 5=2, 6=44, 7=4, 15=1, 196=1, 198=1, 204=1, 209=1, 210=1, 233=1, 265=1}

如上这是每帧总耗时的分布,耗时3ms的有8个,耗时4ms的有8个

我们可以制定自己的标准,诸如单帧耗时30ms 且60ms且200为严重。

7 数据统计

首先有一个大的原则,帧耗时统计是在有渲染动作发生时统计,空闲状态不统计。

帧率的统计就是,渲染帧的数量除以有帧渲染发生动作时间得到。

另,每帧的耗时不尽相同,希望抓住主线,针对性的统计慢帧冻帧的数量以及占比。或者切割的更为精细,如Matrix里默认的把帧的耗时表现分为四个等级。

正常帧,


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有