Android 屏幕刷新机制:ViewRootImpl、Choreographer、Surface、SurfaceFlinger关系详解手机开发

前面有学习了Android绘制的三大流程:merge、layout、draw,但是一直都没有搞清楚绘制到显示的整体流程。借此机会,记录下自己学习过程。

我们都知道,一次完整的录制时都是从ViewRootImpl的scheduleTraversals()开始,即使调用invalidate()也是如此。

scheduleTraversals

 // ViewRootImpl.java 
 void scheduleTraversals() {
    
        // mTraversalScheduled阻止了多次调用 
        if (!mTraversalScheduled) {
    
            mTraversalScheduled = true; 
            // 加入同步屏障 
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); 
            // 加入一个callback到mChoreographer 
            mChoreographer.postCallback( 
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 
            if (!mUnbufferedInputDispatch) {
    
                scheduleConsumeBatchedInput(); 
            } 
            notifyRendererOfFramePending(); 
            pokeDrawLockIfNeeded(); 
        } 
    } 

可以看到scheduleTraversals()主要做了两件事:

  1. 往当前线程的Loop加入同步屏障
  2. 封装了一个mTraversalRunnable加入到mChoreographer

这里就引申出来三个问题:

  1. mTraversalRunnable是什么?
  2. mChoreographer是什么?
  3. 同步屏障是什么?有什么用?

mTraversableRunnable

其实TraversableRunnable分装着就是measure、layout、draw三大流程。

// ViewRootImpl 
final class TraversalRunnable implements Runnable {
    
    @Override 
    public void run() {
    
        doTraversal(); 
    } 
} 
 
void doTraversal() {
    
    if (mTraversalScheduled) {
    
        mTraversalScheduled = false; 
        // 移除同步屏障 
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); 
        performTraversals(); 
    } 
} 
 
private void performTraversals() {
    
    final View host = mView; 
	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 
	performLayout(lp, mWidth, mHeight); 
    performDraw(); 
} 

可见,TraversalRunnable就是一个Runnable,里面做了两件事情:

  1. 移除同步屏障
  2. 开始真正的绘制流程。

总结而言就是一个图:
在这里插入图片描述
那上面我们知道,Choreographer加入了TraversalRunnable,那什么时候去执行TraversalRunnable呢?

Choreographer

Choreographer 翻译为编舞者,负责从显示系统接收脉冲信号,编排渲染下一帧的绘制工作,负责获取Vsync同步信号并控制UI线程完成图像绘制的类。

怎么理解这一句话呢?

一次完整的绘制,是需要CPU、GPU和显示设备的配合,但是三者是一个并行运作的状态,那怎么相互协调呢?所以引进了VSync信号机制:每16ms,硬件(或者软件)会发出一个VSync信号,CPU接收到这个信号,开始了一次绘制流程。再下一次VSync信号到来之时,Display就可以直接显示第一帧,CPU也开始绘制第二帧。就这样循环下去。

也就是说CPU和GPU必须要在这一次的VSync信号发生和下一次VSync信号到来之前要准备好这一帧的数据,否则就发生了掉帧的现象了。

可以看下图,大致可以了解VSync信号机制:
在这里插入图片描述

Choreographer怎么跟Vsync信号机制相挂钩呢?

Choreographer负责订阅Vsync信号和收到Vsycn信号时候,负责去开始一次绘制,具体我们可以看下源码:

我们从上面的ViewRootImpl将mTraversalRunnable封装加入mChoreographer中入手:

// ViewRootImpl 
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 
 
// Choreographer 
public void postCallback(int callbackType, Runnable action, Object token) {
    
    postCallbackDelayed(callbackType, action, token, 0); 
} 
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
    
    synchronized (mLock) {
    
        final long now = SystemClock.uptimeMillis(); 
        final long dueTime = now + delayMillis; 
        // 加入mCallbackQueues 
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); 
		 
		// 这里的delayMills为0,所以会走到scheduleFrameLocked() 
        if (dueTime <= now) {
     
            scheduleFrameLocked(now); 
        } 
        ... 
    } 
} 
private void scheduleFrameLocked(long now) {
    
    if (!mFrameScheduled) {
    
        mFrameScheduled = true; 
        ... 
        // 判断当前thread是否运行loop,这个后续会讲到 
        if (isRunningOnLooperThreadLocked()) {
    
            scheduleVsyncLocked(); 
        } 
   } 
} 
private void scheduleVsyncLocked() {
    
    // 注册订阅了vsync信号 
    mDisplayEventReceiver.scheduleVsync(); 
} 
// DisplayEventReceiver 
public void scheduleVsync() {
    
    ... 
    // 注册订阅了vsync信号 
    nativeScheduleVsync(mReceiverPtr); 
} 
private static native void nativeScheduleVsync(long receiverPtr); 

可以看到主要是两个动作:

  1. mCallbackQueues加入了Runnable
  2. 从jni层注册了Vsync信号
那什么时候会收到Vsync信号回来呢?

在DisplayEventReceiver的disjpatchVsync()方法可以看到,该方法是jni层调用的

// DisplayEventReceiver,由jni调用 
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
    
    onVsync(timestampNanos, builtInDisplayId, frame); 
} 
 
//Choreographer内部类DisplayEventReceiver,重写了onVsync方法 
@Override 
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
    
	mTimestampNanos = timestampNanos; 
    mFrame = frame; 
    Message msg = Message.obtain(mHandler, this); 
    // 设置成异步消息 
    msg.setAsynchronous(true); 
    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); 
} 
 
public void run() {
    
    mHavePendingVsync = false; 
    doFrame(mTimestampNanos, mFrame); 
} 

这里可以看到,其实mHandler就是当前主线程的handler,当接收到onVsync信号的时候,将自己封装到Message中,等到Looper处理,最后Looper处理消息的时候就会调用run方法,这里是Handler的机制,不做解释。

在run方法中,调用了doFrame()方法:

// Choreographer 
void doFrame(long frameTimeNanos, int frame) {
    
	... 
	doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); 
} 
void doCallbacks(int callbackType, long frameTimeNanos) {
    
    CallbackRecord callbacks; 
    // 从mCallbackQueues取出 
    callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked( 
                    now / TimeUtils.NANOS_PER_MS); 
    for (CallbackRecord c = callbacks; c != null; c = c.next) {
    
         c.run(frameTimeNanos); 
    } 
} 
// CallbackRecord 
public void run(long frameTimeNanos) {
    
    if (token == FRAME_CALLBACK_TOKEN) {
    
        ((FrameCallback)action).doFrame(frameTimeNanos); 
    } else {
    
        // 这里也即是调用了TraservalRunnable的run方法,也即是三个绘制流程 
        ((Runnable)action).run(); 
    } 
}     

可见当vsync信号来临的时候,主要做了两件事情

  1. 从mCallbackQueues取出callback
  2. 执行callback,这里最后会执行到TraservalRunnable的三大绘制流程

这里是不是说,Choreographer是不是只要注册一次以后,都可以收到Vsync信号呢?

其实不是的,Vsync信号需要每次都去注册,而且只能接收到一次。这样能保证在不需要重新绘制的情况下,Choreographer也就不需要接收信号,就不会去注册Vsync信号。
在这里插入图片描述

Choreographer在哪里初始化的?

以上介绍了Choreographer是如何发挥作用的,但是Choregrapher是在哪里初始化的?

 // Choreographer 
 public static Choreographer getInstance() {
    
    return sThreadInstance.get(); 
 } 
  
 private static final ThreadLocal<Choreographer> sThreadInstance = 
            new ThreadLocal<Choreographer>() {
    
        @Override 
        protected Choreographer initialValue() {
    
            Looper looper = Looper.myLooper(); 
            if (looper == null) {
    
                throw new IllegalStateException("The current thread must have a looper!"); 
            } 
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP); 
            if (looper == Looper.getMainLooper()) {
    
                mMainInstance = choreographer; 
            } 
            return choreographer; 
        } 
}; 

由上面的代码可以看到:Choreographer存在于Looper的线程的ThreadLocal中,每个looper的线程可能都有一个Choreographer。

而且在ViewRootImpl中可以看到他的身影,这部分不继续介绍了。

同步屏障

上面出现了三个东西关于同步屏障:
1. 加入同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();

2. 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

3. 设置成异步消息
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);

同步屏障

顾名思义就是在阻止同步消息的运行,而异步消息可以直接运行。而我们日常使用的Message都是同步消息。

这得从Handler的机制说起,在MessageQueue中有一个消息链表,类似下图,我们的Message会根据执行时间排序,在next()方法依次取出下一个需要执行的Message,如果加入了同步屏障,则会滤过同步消息,直接取异步消息执行,直到屏障被移除掉。

** 为什么要这么做呢?**

原因是确保异步消息尽可能快的被执行,在这里是确保mTraversalRunnable优先被执行。
在这里插入图片描述

Surface 简单了解

以上只是介绍了绘制流程怎么跟VSync信号相结合,但是并没有说到绘制流程怎么显示到显示器中,这个涉及到Surface的概念,这部分我只是了解了一部分。

surface 存在于ViewRootImpl中

// ViewRootImpl 
public final Surface mSurface = new Surface(); 
// Surface 
public Surface() {
   } 

但是,Surface 还没真正的初始化,这里并没有这么简单,他是连接显示系统的关键,真正的初始化在WMS中,在Acitivity中有讲到窗口机制,在WMS创建对应的Surface,再映射到ViewRootImpl的Surface中。ViewRoot对Surface绘制,WMS对Surface初始化及操作。

而在ViewRootImpl中draw方法中会调用drawSoftware,内部实际上是对Surface的canvas的绘制

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, 
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    
	... 
	canvas = mSurface.lockCanvas(dirty); 
	... 
	mView.draw(canvas); 
    ... 
    surface.unlockCanvasAndPost(canvas); 
} 

以上大致是,ViewRootImpl会绘制操作到Surface,而Surface是跟WMS共享的,

SurfaceFinger 简单了解

SurfaceFlinger是运行在一个独立的进程,由init进程启动。

WMS在创建Surface的时候会通过SurfaceSession中间会话层去请求SurfaceFlinger去创建的对应的Layer

SurfaceFlinger 会维持Layer的Z轴序列,并对Layer最终计算输出

对于SurfaceFlinger太复杂了,后面需要去更多的了解。

Choreographer 做帧率检测

        Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    
            @Override 
            public void doFrame(long frameTimeNanos) {
    
                checkFramesInternal(frameTimeNanos); 
                Choreographer.getInstance().postFrameCallback(this); 
            } 
        }); 

参考

http://gityuan.com/2017/02/25/choreographer/
https://www.jianshu.com/p/75139692b8e6
https://blog.csdn.net/windskier/article/details/7041610

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/app/6256.html

(0)
上一篇 2021年7月17日 00:44
下一篇 2021年7月17日 00:45

相关推荐

发表回复

登录后才能评论