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

2023-04-10  本文已影响0人  艾瑞败类

作者:Drummor

1 什么是帧率

帧率(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);
         }
     }
# Choreographer
private final CallbackQueue[] mCallbackQueues; 
  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);
        ...
    }

补充

5 帧率监控进化之二 滑动帧率

#View
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        ...
        final AttachInfo ai = mAttachInfo;
        if (ai != null) {
            ai.mViewScrollChanged = true;
        }
      ...
    }
#ViewRootImpl
    private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
        ...
        if (mAttachInfo.mViewScrollChanged) {
            mAttachInfo.mViewScrollChanged = false;
            mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
        }
    }
       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为优秀,单帧耗时>30ms 且<60ms为正常,单帧耗时>60ms且<200ms为过高,单帧>200为严重。

7 数据统计

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

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

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

再有就是,如通过adb shell dumpsys gfxinfo packagename命令或者FrameMetricsAggregator里的统计方式,把相同耗时的帧进行合并。

帧的统计往往以page(Activity)为维度,作为一个数据大盘数据。

8 其他

上一篇 下一篇

猜你喜欢

热点阅读