面试问帧率优化,看看matrix是怎么做的

1,094 阅读3分钟

帧率监测主要是监测1秒内刷新的帧数,本笔记主要是分析matrix库的帧率检测原理。

在不同的android系统版本下,帧率检测原理也不一样:

  • 大于等于8.0系统:采用FrameMetrics来检测。

  • 小于8.0 && 大于等于4.1:利用looper.print和choreographer来检测。

FrameMetrics检测原理:

  1. 监听Activity生命周期

1.png

  1. 注册FrameMetrics的Listener:在FrameMetrics这篇笔记里,主要是在Activity.onCreate方法中,调用getWindow().addOnFrameMetricsAvailableListener来设置,listener.onFrameMetricsAvailable这个方法会在每一帧渲染测量完毕后,都会被回调。

重点:该listener注册后,并不会每隔16ms就回调一下,而是页面刷新,有帧需要被绘制了才回调。

比如,activity刚启动后,设置setContentView会触发回调,直到页面被绘制完毕。但是如果接下来10秒页面没有被滑动、触摸、动画等,处于静止状态,该listener并不会被回调。

2.png 说一下FrameMetrics计算丢失帧数的方法:

  1. 获取到每帧数据后,记录该帧的相关指标在列表中:

endNs指的是CPU开始处理绘制(主线程接受到消息,开始处理)。intendedFrameTimeNs指的是从底层接受到vsync信号的时间。如果两者相差大,说明主线程繁忙,来不及响应消息,因此用这个来计算丢帧。

3.png

  1. 当列表大于多少时,触发分析上报:

4.png

WechatIMG64.png

大概原理图:

5.png

着重理解一下蓝色最后两个框的意思:

  • 某activity收集到所有帧的总时长超过10秒:这个指的是页面在有刷新的10秒时间,当页面静止没有刷新时,时间不记录在内。 6.png

  • 上报该页面10s内的fps:参考上图的计算。

7.png

Choreographer:

网上有说用Choreographer.getInstance().postFrameCallback:

//记录上次的帧时间 
private long mLastFrameTime; 
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() { 

    @Override 
    public void doFrame(long frameTimeNanos) { 
        //每500毫秒重新赋值一次最新的帧时间 
        if (mLastFrameTime == 0) { 
            mLastFrameTime = frameTimeNanos; 
        } 

        //本次帧开始时间减去上次的时间除以100万,得到毫秒的差值 
        float diff = (frameTimeNanos - mLastFrameTime) / 1000000.0f; 

        //这里是500毫秒输出一次帧率 
        if (diff > 500) {     
            double fps = (((double) (mFrameCount * 1000L)) / diff); 
            mFrameCount = 0; mLastFrameTime = 0; 
            Log.d("doFrame", "doFrame: " + fps); 
        } else { 
            ++mFrameCount; 
        } 

        //注册监听下一次 vsync信号 
        Choreographer.getInstance().postFrameCallback(this); 
    }
});

但是matrix不是用这种办法,这种会导致不断的去请求vsync信号来计算帧率。matrix方案为:

  1. 给主线程设置looper.printer监听(并不主动请求vsync信号监听,这是相比于旧方案的优势)
  2. 给choreographer的input callback类型设置runnable。因为每次vsync信号回调时,都会切换到UI线程,并调用这个callback的runnable去执行。(choreographer里面总共有3中callback类型,我们平时熟悉的就是traversal,这个是去做measure、layout操作的)。这三种callback类型,先给input类型的runnable去处理,再给animation的处理,再给traversal的处理。因此在初始化时,我们只需要给input的设置就行。
  3. 开始监听主线程消息。
  4. 如果是普通的主线程消息(并不是从底层vsync信号过来的),会触发priner.dispatchBegin,消息处理完再触发dispatchEnd。但是由于该消息从头到尾没有设置过是frame消息,因此该消息忽略。
  5. 如果是从底层vsync消息过来,会触发printer.dispatchBegin,消息处理过程中触发了choreographer的input callback的runnable,因此给这个消息打了一个frame消息的标记位。触发了input的runnable后要立马给animation callback设置runnable。等input callback所有runnable都执行完(里面runnable以列表形式存储),就会执行animation callback的runnable。在animation callback的runnable执行时,就可以记录input callback所有runnable执行了多久,并且给choreographer给traversal callback设置runnable。依次类推就是为了记录这几个类型的runnable到底耗时多久。
  6. 因为底层vsync过来的消息被打了frame消息标记,因此该消息被记录。后面逻辑参照frameMetrics。

8.png