【Android话题-6.1UI体系相关】说说屏幕刷新机制

相关几个问题

  • 丢帧一般是什么原因引起的?
  • Android刷新频率60帧/秒,每隔16ms调ondraw绘制一次?
  • onDraw完之后屏幕会马上刷新么?
  • 如果界面没有重绘,还会每隔16ms刷新屏幕么?
  • 如果屏幕快要刷新的时候才去onDraw绘制会丢帧么?

屏幕显示原理

在这里插入图片描述

  • 首先应用从系统服务申请一段buffer,
  • 然后系统服务返回这个buffer
  • 应用在收到这个buffer之后就可以进行绘制,
  • 绘制完之后就提交给系统服务
  • 然后系统服务把这个buffer写到屏幕的缓冲区里
  • 屏幕会以一定的频率刷新,刷新的时候就会从缓冲区里把图像数据读出来然后显示出来
  • 如果缓冲区里没有新数据,那么屏幕就会一直用老的数据
    屏幕有两个缓存,一个用来显示,另一个用来写入,当屏幕要显示下一张图像的时候就把两个缓存交换:
    在这里插入图片描述

应用端是什么时候开始绘制的?

在这里插入图片描述
屏幕是根据Vsync信号周期性刷新的,vsync是一个固定频率的脉冲信号。屏幕每次收到一个vsync信号就会从缓冲区里读取一帧数据出来显示。而绘制是由应用端发起的,应用随机都可能发起绘制。上图中:
第一个脉冲周期:屏幕上显示的是第0帧图像;
第二个脉冲周期:屏幕上显示的是第一帧,第一个vsync信号来的时候第一帧已经画完了;
第三个脉冲周期:还是显示第一帧,因为第二个vsync信号来的时候第二帧还没画完。第二帧没画完并不是因为第二帧绘制太耗时,其实第二帧优化得还不错,它的耗时远远小于一个时钟周期,但是它比较倒霉,它是在vsync信号快来的时候才开始绘制的,因此它只能等到下一个周期,但第三个vsync信号来的时候第三帧已经画完了,因此第2帧会丢帧。
如果这种情况频发地发生,那么用户会明显感觉到卡顿,就算应用层优化得再好也没用, 因为这是底层刷新机制的缺陷导致的。

这种情况如何优化呢?

其实很简单,如果绘制也能跟vsync一个节奏的话那这个问题就可以解决了。
在这里插入图片描述
每次vsync信号过来的时候,咱们一方面屏幕获取图像数据刷新界面,另一方面应用开始绘制来准备下一帧数据,如果应用优化得好,使得每次绘制都能控制在16ms以内,那么显示就会非常流畅。
但是有一个问题:应用中的重绘一般都是调用requestLayout(),这个函数随时都能调用,怎么能够让它受你的控制让它只在vsync信号来的时候才开始重绘呢?Android系统是怎么做的呢?有一个关键的类:Choreographer

Choreographer是什么?

Choreographer 翻译过来就是舞蹈指导,它有什么用呢?它最大的作用是:你往里面发一个消息,这个消息最快也要等到下一个vsync信号来了才能触发。比如说,绘制是随时发起的,你封装一个runnable丢给Choreographer,然后下一个vsync信号来的时候就开始处理消息,然后就开始真正的界面重绘——相当于UI绘制的节奏完全由Choreographer来控制。下面说说Choreographer底层的实现原理。

Choreographer的实现原理

//这个函数主要用于发起UI重绘
public void requestLayout(){
  ...
  scheduleTraversals();
}

void scheduleTraversals(){
  //往消息队列里插了一个syncBarrier
  mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
  //往Choreographer队列里插了一个CALLBACK_TRAVERSAL
  mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  ...
}

先说说syncBarrier:
这其实是一个消息屏障,把这个屏障插到消息队列里之后,它后面的消息就不能处理,一直等到这个屏障撤除之后才能处理。但是这个屏障对异步消息是没有影响的。为什么需要这样一种机制呢?主要是因为有些消息的类型非常紧急必须马上处理,如果队列中的普通消息太多是非常耽误事的,所以这里插了一个屏障,把普通消息先挡在一边,优先处理这些异步消息。比如我们往Choreographer里丢的这个runnable它其实就是一个异步消息,等到下一个vsync信号来的时候这个异步消息是在紧急处理的。

mChoreographer的创建

这是跟ViewRootImpl一起创建的

public static Choreographer getInstance(){
  return sThreadInstance.get();
}

这个getInstance看起来像是一个单例,但其实不是!sThreadInstance是一个ThreadLocal,也就是说你在不同的线程去调用getInstance()的时候它返回的是不同的Choreographer对象。

再说requestLayout()

如果有人一口气调用了10次requestLayout(),那么是否意味着到下一次vsync到来的时候会触发10次重绘呢?当然不是!

void scheduleTraversals(){
  //mTraversalScheduled是一个bool变量,只有当它为false的时候才会向Choreographer里PostCallback
  if(!mTraversalScheduled) {
    //另外会在doTraversal里把mTraversalScheduled 置成false
    mTraversalScheduled = true;
    mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    ...
  }
}

void doTraversal(){
  if(mTraversalScheduled) {
    mTraversalScheduled = false;
    performTraversals();
  }
}
  • 强调一个mTraversalRunnable是在下一次vsync信号来的时候才会被执行的
  • 只有在mTraversalScheduled 设置成false之后才代表它会接受新传进来的requestLayout
  • 因此我们可以看到,在一个vsync周期里,最多只会有一个requestLayout有效,最终只触发一次界面重绘

callBack是如何加到Choreographer里的?

private void postCallbackDelayedInternal(int callbackType, ...) {
  ...
  //mCallbackQueues中每个元素都是一个callback单链表
  //这一方面要根据callbackType插入到对应的callback单链表里
  //另一方面要根据执行的时间顺序来排序:越是马上要发生的callback越要排在链表的前面
  mCallbackQueues[callbackType].addCallbackLocked(duTime, ...);
  ...
  scheduleFrameLocked(now);
}

private void scheduleFrameLocked(long now) {
  if (isRunningOnLooperThreadLocked()) {
    //如果当前线程就是Choreographer的工作线程,直接调用scheduleVsyncLocked
    scheduleVsyncLocked();
  }else{
    //否则发一个MSG_DO_SCHEDULE_VSYNC消息到Choreographer的工作线程中
    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
    msg.setAsynchronous(true);
    mHandler.sendMessageAtFrontOfQueue(msg);
  }
}

请注意这是一个异步消息,它不受屏障消息的影响,而且是插到队列的头部,可见这个消息非常紧急————因为要马上告知SurfaceFlinger:在下一个vsync信号来的时候一定要第一时间通知我,如果错过了这个vsync,绘制就得多等一个周期。

发过多MSG_DO_SCHEDULE_VSYNC之后将发生什么呢?

其实是要告诉SurfaceFlinger:我要关注下一个vsync信号。因此当下一个vsync到来的时候SurfaceFling就会通知我们并回调到FrameDisplayEventReceiver 的onVsync函数

class FrameDisplayEventReceiver extends DisplayEventReceiver implement Runnable {
  @Override
  public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
    //timestampNanos是vsync信号的时间戳
    ...
    mTimestampNanos = timestampNanos;
    mFrame = frame;
    Message msg = Message.obtain(mHandler, this);
    msg.setAsynchronous(true);
    //封装一个消息发到Choreographer的工作线程中,这并非切换工作线程:此时onVsync已经是在Choreographer的工作线程中mHandler也是。
    //timestampNanos表示消息要触发的时间,有了这个时间戳就可以按时间戳的顺序来处理消息
    //时间到了之后将调用下面的run函数
    mHandler.sendMessageAtTime(msg, timestampNanos/TimeUtils.NANOS_PER_MS);
  }

  @Override
  public void run() {
    doFrame(mTimestampNanos, mFrame);
  }
}
void doFrame(long frameTimeNanos, int frame) {
  //frameTimeNanos表示这一帧的时间戳
  long intendedFrameTimeNanos = frameTimeNanos;
  long startNanos = System.nanoTime();
  //先计算当前时间与时间戳有多大,结果越大表示这一帧已经延时得越厉害
  final long jitterNanos = startNanos - frameTimeNanos;
  //如果延时超过一个周期
  if (jitterNanos >= mFrameIntervalNanos) {
    //计算一个延时了多少个周期
    final long skippedFrames = jitterNanos/mFrameIntervalNanos;
    if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
      //如果跳过的帧数大于某个阀值(默认30),将打印一个警告:
      //应用在主线程做太多事情,导致把绘制耽误了,跳过了好多帧
      Log.i(TAG, "Skipped" + skippedFrames + "frames! "
        + "The application may be doing too much work on its main thread.");
    final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
    frameTimeNanos = startNanos - lastFrameOffset;
  }
  //处理callback:前面讲过有4种Callback,第种callback对应一个callbackQueue,也就是单链表
  //这里把vsync信号分别发给4种callback,然后执行对应的doCallbacks函数
  doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
  doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
  doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
  doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}

void doCallbacks(int callbackType, long frameTimeNanos) {
  CallbackRecord callbacks;
  //CallbackQueues中的callback是有时间戳的,只有时间到了才会执行对应的callback
  //extractDueCallbacksLocked是从mCallbackQueues中取出那些到执行时间的callback
  callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(...);
  for (CallbackRecord c = callbacks; c != null; c = c.next) {
    //分别执行callback的run函数
    c.run(frameTimeNanos);
  }
}

再回顾一个前面scheduleTraversals传的callback是什么样的:

void scheduleTraversals(){
  if(!mTraversalScheduled){
    mTraversalScheduled = true;
    //mTraversalRunnable见下面
    mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    ...
  }
}

final class TraversalRunnable implements Runnable{
  @Override
  public void run() {
    //最终调用的是performTraversal:这才是真正执行绘制的函数!
    doTraversal();
  }
}

总结一下前面讲的流程

在这里插入图片描述

  • 首先应用层的View调用了requestLayout要求重绘,其实是new了一个runnable丢到choreographer的队列里
  • choreographer并没有马上去处理这个消息,它先通过requestNextVsync向SurfaceFlinger请求下一个vsync信号
  • SurfaceFlinger会在下一个vsync信号来的时候通过postSyncEvent向choreographer发送通知
  • choreographer收到通知后就会去处理消息队列里的消息
  • 之前的requestLayout对应的runnbale执行的是performTraversal()函数去真正执行绘制。

scheduleVsyncLocked的实现

这个函数是用来告诉SurfaceFlinger:下一个vsync信号来的时候通知我

private void scheduleVsyncLocked() {
  mDisplayEventReceiver.sheduleVsync();
}

//NativeDisplayEventReceiver是mDisplayEventReceiver在native层的一个对象
sttatus_t NativeDisplayEventReceiver::scheduleVsync() {
  ...
  status_t status = mReceiver.requestNextVsync();
  ...
}

status_t DisplayEventReceiver::requestNextVsync() {
  //请求下一个vsync
  mEventConnection->requestNextVsync();
}

mEventConnection是啥?看看它的创建:

DisplayEventReceiver::DisplayEventReceiver(){
  //首先拿到suffaceFlinger的binder句柄
  sp<ISurfaceComposer> sf(ComposerService::getComposerService());
  if(sf!=NULL){
    //得到另一个句柄
    //这种套路很常见:比如说拿到服务服务的binder句柄之后要么onpenSession,要么去create一个Connection
    //总之就是单独搞一个通道!
    mEventConnection = sf->createDisplayEventConnection();
    //这个channel是在Connection创建的时候new的的一个BitTube对象,见下面
    mDataChannel = mEventConnection->getDataChannel();
  }
}

sp<BitTube> EventThread::Connection::getDataChannel() const {
  return mChannel;
}

BitTube

socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets);
  • BiTube其实就是通过socketpair创建的两个描述符:mSendFd/mReceiverFd
  • mSendFd用于发送,mReceiverFd用于接收
  • 往mSendFd中写数据,将可从mReceiverFd中读出——跟管道类似

Connction是如何创建的

在SurfaceFlinger进程中

sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection(){
  //EventThread就是一个不停等待并处理event的thread
  //vsync信号就是它要等待的event之一
  return mEventThread->createEventConnection();
}

sp<EventThread::Connection> EventThread::createEventConnection() const {
  return new Connection(const_cast<EventThread*>(this));
}

EventThread::Connection::Connection(const sp<EventThread>& eventThread)
  :count(-1), mEventThread(eventThread), mChannel(new BitTub()){
}

void EventTHread::Connection::onFirstRef(){
  //connection把自己注册到eventThread中,
  //这样当eventThread中有事件的时候就能分发到connection,connection就能调到应用进程了
  mEventThread->registerDisplayEventConnection(this);
}

conncetion的是如何注册到EventThread的:

status_t EventThread::registerDisplayEventConnction(const sp<EventThread::Connection>& connection){
  mDisplayEventConnections.add(connection);
  //类似java中的notifyAll:
  mCondition.broadcast();
}

//EventThread的创建
void SurfaceFlinger::init() {
  //创建vsync的信号源
  sp<VsyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync, ...);
  mEventThread = new EventThread(vsyncSrc);
  ...
}

//EventThread启动之后将会不停地执行threadLoop函数
bool EventThread::threadLoop(){
  DisplayEventReceiver::Event event;
  Vector< sp<EventThread::Connection>> signalConnections;
  signalConnections = waitForEvent(&event);

  const size_t count = signalConnections.size();
  for (size_t i=0; i<count; i++){
    const sp<Connection>& conn(signalConnections[i]);
    //分发事件:典型的就是vsync事件
    conn->postEvent(event);
  }
  return true;
}

waitForEvent

Vector<sp<EventThread::Connection>> EventThread::waitForEvent(...){
  Vector<sp<EventThread::Connection>> signalConnections;
  do{
    //看是否已经有vsync信号来了
    //如果有的话,就uwtlconnection列表返回
    //如果没有的话,就等待vsync信号
    ...
  }while(signalConnections.isEmpty());
  return signalConnections;
}


```c
void EventThread::requestNextVsync(const sp<EventTHread::Connection>& connection){
  if(connection->count < 0) {
    connection->count = 0;
    mCondition.broadcast();
  }
}
  • 注意connection中有一个count字段,只有count>=0的时候才表示它关注vsync事件,
  • 应用端调用requestNextVsync的时候其实就是把其中的count从-1改成0
  • 当我们把connection加到signalConnections列表中的时候又自动把count设置-1
  • 如果想要断续关注vsync信号应得重新调用一次requestNextVsync函数

事件是如何通过Connection分发出去的

status_t EventThread::Connection::postEvent(const DisplayEventReceiver::Event& event{
  size_t size = DisplayEventReceiver::sendEvents(mChanel, &event, 1);
  return size < 0? status_t(size):status_t(NO_ERROR);
}

size_t DisplayEventReceiver::sendEvetns(const sp<Bittube>& DataChannel, Event const* events, size_t count){
  return BitTube::sendObjects(dataChannel, events, count);
}

size_t BitTube::sendObjects(const sp<BitTube>& tube, ...){
  const char* vaddr = reinterpret_cast<const char*>(events);
  //bitTube像管道,有两个描述符一个读一个写,这里往管道的一端写,读的另一端将收到通知
  ssize_t size = tube->write(vaddr, count*objeSize);
  return size < 0? size:size/static_cast<size_t>(objSize);
}

两个问题:

  • bitTube中的读句柄是如何传到应用端的
  • 应用是如何监听读事件的

bitTube中的读句柄是如何传到应用端的

回到DisplayEventReceiver的构造函数:

DisplayEventReceiver::DisplayEventReceiver(){
  sp<ISurfaceComposer> sf(ComposerService::getComposerService());
  mEventConnection = sf->createDisplayEventConnection();
  mDataChannel = mEventConnection->getDataChannel();
}
//应用端:
virtual sp<BitTube> getDataCannel() const {
  Parcel data, reply;
  //发出获取BitTube请求
  data.writeInterfaceToken(IDisplayEventConnction::getInterfaceDescriptor());
  remote()->transact(GET_DATA_CHANNEL, data, &reply);
  //不原出BitTube
  return new BitTube(reply);
}

//SurfaceFlinger端
sp<BitTube> EventThread::Connection::getDataChannel() const{
  //直接返回mChannel
  return mChannel;
}

应用是如何监听读事件的

private Choreographer(Looper looper){
  ...
  //FrameDispalyEventReceiver继承了DesplayEventReceiver
  mDisplayEventReceiver = new FrameDispalyEventReceiver(looper);
}

public DisplayEventReceiver(Looper looper) {
  ...
  mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this);
}

static jLong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, ...){
  ...
  sp<NativeDisplayEventReceiver> receiver = new NativeDispalyEventReceiver(...);
  status_t status = receiver->initialize();
  return reinterpret_cast<jlong>(receiver.get());
}

status_t NativeDisplayEventReceiver::initialize(){
  //往Looper中添加一个描述符,就是让Looper一起监听读事件
  //this是一个调回,表示当迷个fd有可读元事件的时候将触发这个回调
  mMessageQueue->getLooper()->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT, this, NULL);
  return OK;
}

DisplayEventReceiver::DisplayEventReceiver(){
  sp<ISurfaceComposer> sf(ComposerService::getComposerService());
  mEventConnection = sf->createDisplayEventConnection();
  mDataChannel = mEventConnetion->getDataChannel();
}

int DisplayEventReceiver::getFd() const {
  return mDataChannel->getFd();
}

int BitTube::getFd() const {
  return mReceiverFd;
}

mReceiverFd是如何添加到Looper中的:

int Looper::addFd(int fd, int ident, int events, ...){
  Request request;
  request.fd = fd;
  ...
  struct epool_event eventItem;
  request.intEventItem(&eventItem);

  int epollResult = epoll_ctl(mEpoolFd, EPOLL_CTL_ADD, fd, & eventItem);
  mRequests.add(fd, request);
}

Looper是怎么检测fd的读事件的:

int Looper::pollInner(int timeoutMillis){
  ...
  int eventCount = epoll_wait(mEpoolFd, eventItems, ...);

  for(int i = 0; i < eventCount; i++){
    int fd = eventItems[i].data.fd;
    uint32_t epollEvents = eventItems[i].events;
    if(fd == mWakeEventFd){
      //消息队列的事件
      ...
    }else{
      if(epollEvents&EPOLLIN) events |= Event_INPUT;
      //先放到队列中,等for循环结束后统一处理
      pushResponse(events, mRequests.valueAt(requestIndex));
    }
  }
  //统一处理Responses
  for (size_t i = 0; i < mResponses.size(); i++){
    Response& response = mResponses.editItemAt(i);
    int fd = response.request.fd;
    int events = response.events;
    void* data = response.request.data;
    //调用每一个callback的handleEvent函数
    int callbackResult = response.request.callback->handleEvent(fd, events, data);
    if(callbackResult == 0){
      //callback的返回值很重要,如果返回0,将把fd从移除,不再检测
      removeFd(fd, response.request.seq);
    }
    response.equest.callback.clear();
    result = POLL_CALLBACK;
  }
  return result;
}

前面BitTume添加的回调:

int NativeDisplayEventReceiver::handleEvent(int receiveFd, int events, ...){
  //把SurfaceFlinger发过来的事件读出来
  if(processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, ...)){
    mWaitingForVsync = false;
    //把它分发出去
    dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
  }
  //返回1,表示将继续监听fd的事件
  return 1;
}

void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, ...){
  //调用Java函数
  env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync, timestamp, ...);
}

void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame){
  //处理Choreographer的callback
  onVsync(timestampNanos, builtInDisplayId, frame);
}

回归:回答这几个问题?

  • 丢帧一般是什么原因引起的?
    a)主线程耗时操作耽误了View的绘制,所以丢帧了
  • Android刷新频率60帧/秒,每隔16ms调用onDraw绘制一次?
    a)刷新频率其实是批vsyn信号发生的频率,但并不是每一次都会去绘制
    b)需要应用端先主动发起重绘,才会向SurfaceFlinger请求接收vsync信号
    c)等到下次vsync信号来的时候都会真正去绘制
  • onDraw完之后屏幕会马上刷新么?
    a)onDraw完了之后不会马上刷新,要等到下次vsync信号来的时候才刷新
  • 如果界面没有重绘,还会每隔16ms刷新屏幕么?
    a)如果没有重绘,应用就不会收到vsync信号,但是屏幕还是会以每秒60帧的频率刷新,只不过画面数据一直用的是旧的,所以看起来没什么变化。
  • 如果在屏幕快要刷新的时候才去绘制会丢帧么?
    a)代码中发起的重绘不会马上执行,等到下次vsync信号来的时候才开始,因此什么时候发起绘制并没有太大的关系

回归:说说Android的UI刷新机制

  • Vsync的原理是怎样的?
  • Choreographer的原理是怎样的?
  • UI刷新的大致流程,应用和SurfaceFlinger的通信过程?
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值