本文正在参加「金石方案 . 瓜分6万现金大奖」

前语

屏幕改写帧率不稳定,掉帧严峻,无法确保每秒60帧,导致屏幕画面撕裂;

今天咱们来讲解下VSYNC机制和UI改写流程

一、 Vsync信号详解

1、屏幕改写相关常识点

  • 屏幕改写频率: 一秒内屏幕改写的次数(一秒内显现了多少帧的图像),单位 Hz(赫兹),如常见的 60 Hz。改写频率取决于硬件的固定参数(不会变的);
  • 逐行扫:显现器并不是一次性将画面显现到屏幕上,而是从左到右边,从上到下逐行扫描,次序显现整屏的一个个像素点,不过这一进程快到人眼无法察觉到改动。以 60 Hz 改写率的屏幕为例,这一进程即 1000 / 60 ≈ 16ms;
  • 帧率: 表示 GPU 在一秒内制作操作的帧数,单位 fps。例如在电影界采用 24 帧的速度足够使画面运转的十分流畅。而 Android 系统则采用更加流程的 60 fps,即每秒钟GPU最多制作 60 帧画面。帧率是动态改动的,例如当画面停止时,GPU 是没有制作操作的,屏幕改写的仍是buffer中的数据,即GPU终究操作的帧数据;
  • 屏幕流畅度:即以每秒60帧(每帧16.6ms)的速度运转,也便是60fps,而且没有任何推迟或许掉帧;
  • FPS:每秒的帧数;
  • 丢帧:在16.6ms完成作业却因各种原因没做完,占了后n个16.6ms的时刻,相当于丢了n帧;

2、VSYNC机制

VSync机制: Android系统每隔16ms宣布VSYNC信号,触发对UI进行渲染,VSync是Vertical Synchronization(笔直同步)的缩写,是一种在PC上很早就广泛使用的技术,能够简单的把它认为是一种定时中止。而在Android 4.1(JB)中现已开端引进VSync机制;

Android的VSYNC机制和UI刷新流程【金石计划】

VSync机制下的制作进程;CPU/GPU接纳vsync信号,Vsync每16ms一次,那么在每次宣布Vsync命令时,CPU都会进行改写的操作。也便是在每个16ms的第一时刻,CPU就会响应Vsync的命令,来进行数据改写的动作。CPU和GPU的改写时刻,和Display的FPS是共同的。由于只有到宣布Vsync命令的时分,CPU和GPU才会进行改写或显现的动作。CPU/GPU接纳vsync信号提前准备下一帧要显现的内容,所以能够及时准备好每一帧的数据,确保画面的流畅;

Android的VSYNC机制和UI刷新流程【金石计划】

可见vsync信号没有提示CPU/GPU作业的状况下,在第一个16ms之内,一切正常。然而在第二个16ms之内,几乎是在时刻段的终究CPU才计算出了数据,交给了Graphics Driver,导致GPU也是在第二段的末尾时刻才进行了制作,整个动作延后到了第三段内。从而影响了下一个画面的制作。这时会呈现Jank(闪烁,能够理解为卡顿或许停顿)。这时分CPU和GPU或许被其他操作占用了,这便是卡顿呈现的原因;

二、UI改写原理流程

1、VSYNC流程示意

当咱们经过setText改动TextView内容后,UI界面不会立刻改动,APP端会先向VSYNC服务恳求,比及下一次VSYNC信号触发后,APP端的UI才真的开端改写,基本流程如下:

Android的VSYNC机制和UI刷新流程【金石计划】

setText终究调用invalidate恳求重绘,终究会经过ViewParent递归到ViewRootImpl的invalidate,恳求VSYNC,在恳求VSYNC的时分,会增加一个同步栅门,避免UI线程中同步音讯履行,这样做为了加快VSYNC的响应速度,假如不设置,VSYNC到来的时分,正在履行一个同步音讯;

2、view的invalidate

View会递归的调用父容器的invalidateChild,逐级回溯,终究走到ViewRootImpl的invalidate

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
      boolean fullInvalidate) {
      // Propagate the damage rectangle to the parent view.
      final AttachInfo ai = mAttachInfo;
      final ViewParent p = mParent;
      if (p != null && ai != null && l < r && t < b) {
        final Rect damage = ai.mTmpInvalRect;
        damage.set(l, t, r, b);
        p.invalidateChild(this, damage);
      }
ViewRootImpl.java
void invalidate() {
  mDirty.set(0, 0, mWidth, mHeight);
  if (!mWillDrawSoon) {
    scheduleTraversals();
  }
}

ViewRootImpl会调用scheduleTraversals准备重绘,可是,重绘一般不会当即履行,而是往Choreographer的Choreographer.CALLBACK_TRAVERSAL行列中增加了一个mTraversalRunnable,一起恳求VSYNC,这个mTraversalRunnable要一直比及恳求的VSYNC到来后才会被履行;

3、scheduleTraversals

ViewRootImpl.java
// 将UI制作的mTraversalRunnable加入到下次笔直同步信号到来的等候callback中去
// mTraversalScheduled用来确保本次Traversals未履行前,不会要求遍历两边,糟蹋16ms内,不需求制作两次
void scheduleTraversals() {
  if (!mTraversalScheduled) {
    mTraversalScheduled = true;
    // 避免同步栅门,同步栅门的意思便是阻拦同步音讯
    mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
    // postCallback的时分,顺便恳求vnsc笔直同步信号scheduleVsyncLocked
    mChoreographer.postCallback(
        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    <!--增加一个处理触摸事情的回调,避免中间有Touch事情过来-->
    if (!mUnbufferedInputDispatch) {
      scheduleConsumeBatchedInput();
    }
    notifyRendererOfFramePending();
    pokeDrawLockIfNeeded();
  }
}

4、恳求VSYNC同步信号

Choreographer常识点在上个文章具体介绍过;

Choreographer.java
private void postCallbackDelayedInternal(int callbackType,
    Object action, Object token, long delayMillis) {
  synchronized (mLock) {
    final long now = SystemClock.uptimeMillis();
    final long dueTime = now + delayMillis;
    mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
    if (dueTime <= now) {
    <!--恳求VSYNC同步信号-->
      scheduleFrameLocked(now);
    }
  }
}

5、scheduleFrameLocked

// mFrameScheduled确保16ms内,只会恳求一次笔直同步信号
// scheduleFrameLocked能够被调用屡次,可是mFrameScheduled确保下一个vsync到来之前,不会有新的恳求宣布
// 剩下的scheduleFrameLocked调用被无效化
private void scheduleFrameLocked(long now) {
  if (!mFrameScheduled) {
    mFrameScheduled = true;
    if (USE_VSYNC) {
      if (isRunningOnLooperThreadLocked()) {
        scheduleVsyncLocked();
      } else {
        // 由于invalid现已有了同步栅门,所以有必要mFrameScheduled,音讯才干被UI线程履行
        Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtFrontOfQueue(msg);
      }
    }
  }
}
  • 在当时恳求的VSYNC到来之前,不会再去恳求新的VSYNC,由于16ms内恳求两个VSYNC没含义;
  • 再VSYNC到来之后,Choreographer利用Handler将FrameDisplayEventReceiver封装成一个异步Message,发送到UI线程的MessageQueue;

6、FrameDisplayEventReceiver

 private final class FrameDisplayEventReceiver extends DisplayEventReceiver
      implements Runnable {
    private boolean mHavePendingVsync;
    private long mTimestampNanos;
    private int mFrame;
    public FrameDisplayEventReceiver(Looper looper) {
      super(looper);
    }
    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
      long now = System.nanoTime();
      if (timestampNanos > now) {
      <!--正常状况,timestampNanos不应该大于now,一般是上传vsync的机制出了问题-->
        timestampNanos = now;
      }
      <!--假如上一个vsync同步信号没履行,那就不应该相应下一个(或许是其他线程经过某种方式恳求的)-->
       if (mHavePendingVsync) {
        Log.w(TAG, "Already have a pending vsync event. There should only be "
            + "one at a time.");
      } else {
        mHavePendingVsync = true;
      }
      <!--timestampNanos其实是本次vsync发生的时刻,从服务端发过来-->
      mTimestampNanos = timestampNanos;
      mFrame = frame;
      Message msg = Message.obtain(mHandler, this);
      <!--由于现已存在同步栅门,所以VSYNC到来的Message需求作为异步音讯发送过去-->
      msg.setAsynchronous(true);
      mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }
    @Override
    public void run() {
      mHavePendingVsync = false;
      <!--这儿的mTimestampNanos其实便是本次Vynsc同步信号到来的时分,可是履行这个音讯的时分,或许推迟了-->
      doFrame(mTimestampNanos, mFrame);
    }
  }
  • 之所以封装成异步Message,是由于前面增加了一个同步栅门,同步音讯不会被履行;
  • UI线程被引发,取出该音讯,终究调用doFrame进行UI改写重绘;

7、doFrame

void doFrame(long frameTimeNanos, int frame) {
  final long startNanos;
  synchronized (mLock) {
  <!--做了许多东西,都是为了确保一次16ms有一次笔直同步信号,有一次input 、改写、重绘-->
    if (!mFrameScheduled) {
      return; // no work to do
    }
   long intendedFrameTimeNanos = frameTimeNanos;
    startNanos = System.nanoTime();
    final long jitterNanos = startNanos - frameTimeNanos;
    <!--检查是否由于推迟履行掉帧,每大于16ms,就多掉一帧-->
    if (jitterNanos >= mFrameIntervalNanos) {
      final long skippedFrames = jitterNanos / mFrameIntervalNanos;
      <!--跳帧,其实便是上一次恳求改写被推迟的时刻,可是这儿skippedFrames为0不代表没有掉帧-->
      if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
      <!--skippedFrames很大必定掉帧,可是为 0,去并非没掉帧-->
        Log.i(TAG, "Skipped " + skippedFrames + " frames! "
            + "The application may be doing too much work on its main thread.");
      }
      final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
        <!--开端doFrame的真实有效时刻戳-->
      frameTimeNanos = startNanos - lastFrameOffset;
    }
    if (frameTimeNanos < mLastFrameTimeNanos) {
      <!--这种状况一般是生成vsync的机制呈现了问题,那就再恳求一次-->
      scheduleVsyncLocked();
      return;
    }
     <!--intendedFrameTimeNanos是原本要制作的时刻戳,frameTimeNanos是真实的,能够在渲染工具中标识推迟VSYNC多少-->
    mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
    <!--移除mFrameScheduled判断,说明处理开端了,-->
    mFrameScheduled = false;
    <!--更新mLastFrameTimeNanos-->
    mLastFrameTimeNanos = frameTimeNanos;
  }
  try {
    <!--真实开端处理业务-->
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
    <!--处理打包的move事情-->
    mFrameInfo.markInputHandlingStart();
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    <!--处理动画-->
    mFrameInfo.markAnimationsStart();
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    <!--处理重绘-->
    mFrameInfo.markPerformTraversalsStart();
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    <!--提交->
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
  } finally {
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  }
}
  • doFrame也采用了一个boolean遍历mFrameScheduled确保每次VSYNC中,只履行一次,能够看到,为了确保16ms只履行一次重绘,加了好屡次层保障;
  • doFrame里除了UI重绘,其实还处理了许多其他的事,比方检测VSYNC被推迟多久履行,掉了多少帧,处理Touch事情(一般是MOVE),处理动画,以及UI;
  • 当doFrame在处理Choreographer.CALLBACK_TRAVERSAL的回调时(mTraversalRunnable),才是真实的开端处理View重绘;
 final class TraversalRunnable implements Runnable {
  @Override
  public void run() {
    doTraversal();
  }
}

回到ViewRootImpl调用doTraversal进行View树遍历;

8、doTraversal

// 这儿是真实履行了,
void doTraversal() {
  if (mTraversalScheduled) {
    mTraversalScheduled = false;
    <!--移除同步栅门,只有重绘才设置了栅门,说明重绘的优先级仍是挺高的,一切的同步音讯有必要让步-->
    mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    performTraversals();
  }
}
  • doTraversal会先将栅门移除,然后处理performTraversals,进行丈量、布局、制作,提交当时帧给SurfaceFlinger进行图层组成显现;
  • 以上多个boolean变量确保了每16ms最多履行一次UI重绘;

9、UI局部重绘

View重绘改写,并不会导致一切View都进行一次measure、layout、draw,只是这个待改写View链路需求调整,剩下的View或许不需求糟蹋精力再来一遍;

View.java
  public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
     ...
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
        || !renderNode.isValid()
        || (mRecreateDisplayList)) {
     <!--失效了,需求重绘-->
    } else {
    <!--仍旧有效,无需重绘-->
      mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
      mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
  }

10、制作总结

  • android最高60FPS,是VSYNC及决定的,每16ms最多一帧;
  • VSYNC要客户端自动恳求,才会有;
  • 有VSYNC到来才会改写;
  • UI没更改,不会恳求VSYNC也就不会改写;

总结

关于制作还有许多常识点,后面会总结陆续宣布来的;