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太复杂了,后面需要去更多的了解。

参考

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

发布了75 篇原创文章 · 获赞 63 · 访问量 3万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览