最近的冠状病毒搞得人心惶惶,待在家里面也没啥事,正好趁这段时间学习一下。了解一下Android屏幕的刷新机制对于我们解决应用卡顿也有帮助。

1.屏幕刷新机制

1)屏幕显示

首先我们看下Android屏幕的显示原理,如下图所示
Android屏幕的刷新机制_第1张图片
应用需要界面重绘时,会向系统申请buffer,拿到buffer之后会将图像信息写入buffer当中提交给系统,然后屏幕从缓冲区拿到图像数据进行显示。
Android手机一般都是60帧/秒,也就是每16ms屏幕从缓冲区读取图像数据进行刷新,如果没有新的图像数据更新,则会一直读到旧的图像数据,显示旧的图像。

2)VSync信号

屏幕都是周期性的刷新,每个周期,系统均会产生一个VSync信号,这样就会产生一个问题,如下图所示
Android屏幕的刷新机制_第2张图片
应用绘制完第一帧,恰好在VSync周期内,然后在下一个周期,屏幕就能够显示第一帧;
但是第二帧绘制的时间处于两个VSync周期之间,所以当第二帧绘制完成之后,只有在第四个周期才能够显示,然后导致第三帧只能在第五个周期显示,然而第三帧在第三个周期内就已经绘制完成。这样就会导致应用的卡顿。
所以为了解决这个问题,在VSync信号来临的时候才进行绘制,如下图所示,让应用的绘制和屏幕的显示保持同步。
而实现保持同步功能的类就是Choreographer。
Android屏幕的刷新机制_第3张图片

2.Choreographer

首先我们来看下ViewRootImpl.requestLayout,应用端请求刷新界面。
我们可以看到在注释2处,增加了一个消息屏障,在屏障之后的所有普通消息都暂时不能够执行,只有撤除屏障之后才能够执行,但是异步消息不受屏障的影响,这样做的好处就是让真正重要的消息能够优先执行。
另外我们需要注意的是,在注释1处,判断了mTraversalScheduled为false,才会post异步消息,而一旦执行post,mTraversalScheduled就被置为true,而只有当下一个VSync信号来临之时,执行了mTraversalRunnable,mTraversalScheduled才能重新被设置为false。所以如果应用端同时发起多次requestLayout,在一个VSync周期内,也只能执行一次绘制。

public void requestLayout() {    if (!mHandlingLayoutInLayoutRequest) {        checkThread();        mLayoutRequested = true;        scheduleTraversals();    }}void scheduleTraversals() {    //1.mTraversalScheduled为false才执行    if (!mTraversalScheduled) {        mTraversalScheduled = true;        //2.增加一个消息屏障        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();        //3.post了一个异步消息        mChoreographer.postCallback(                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);        if (!mUnbufferedInputDispatch) {            scheduleConsumeBatchedInput();        }        notifyRendererOfFramePending();        pokeDrawLockIfNeeded();    }}

接着我们来看下postCallback,最终会执行到postCallbackDelayedInternal,先看注释1,mCallbackQueues是一个单链表数组,根据callbacyType,将action插入到链表当中。
我们传入的delayMills为0,所以会执行到注释2处。
注释3处,如果在当前线程,则直接执行scheduleVsyncLocked,否则使用handler post到指定的线程执行,scheduleVsyncLocked的作用就是告诉SurfaceFlinger,当下一个VSync信号来临的时候,我们能够收到回调。

private void postCallbackDelayedInternal(int callbackType,        Object action, Object token, long delayMillis) {   ......    synchronized (mLock) {        final long now = SystemClock.uptimeMillis();        final long dueTime = now + delayMillis;        //1.单链表,根据callbackType将action插入到链表当中        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);        if (dueTime <= now) {            //2.执行scheduleFrameLocked            scheduleFrameLocked(now);        } else {            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);            msg.arg1 = callbackType;            msg.setAsynchronous(true);            mHandler.sendMessageAtTime(msg, dueTime);        }    }}private void scheduleFrameLocked(long now) {    ......           //3.如果在当前线程,则直接执行scheduleVsyncLocked,否则发送msg到对应的线程执行scheduleVsyncLocked            if (isRunningOnLooperThreadLocked()) {                scheduleVsyncLocked();            } else {                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);                msg.setAsynchronous(true);                mHandler.sendMessageAtFrontOfQueue(msg);            }......}private void scheduleVsyncLocked() {    //4.当VSync信号来临时,mDisplayEventReceiver能够收到回调    mDisplayEventReceiver.scheduleVsync();}

然后我们来看下回调的地方,当VSync信号来临的时候,会回调到onVsync函数里面,然后封装一个msg,使用handler发出,最终会执行run方法里面的doFrame。

private final class FrameDisplayEventReceiver extends DisplayEventReceiver        implements Runnable {    private boolean mHavePendingVsync;    private long mTimestampNanos;    private int mFrame;    public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {        super(looper, vsyncSource);    }    @Override    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {        ......        mTimestampNanos = timestampNanos;        mFrame = frame;        //1.将this传入,即Runnable,msg为一个异步消息        Message msg = Message.obtain(mHandler, this);        msg.setAsynchronous(true);        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);    }    @Override    public void run() {        mHavePendingVsync = false;        doFrame(mTimestampNanos, mFrame);    }}

再来看一下doFrame都干了些啥
首先看下在注释1处,用当前时间减去帧的时间得到延时时间,并用延时时间除以帧的周期,如果超过了规定的常量,就会打印“The application may be doing too much work on its main thread.”,表明应用在主线程做了太多的工作,导致跳了太多帧,需要优化。
注释2处显示了处理各种类型的callback,其中frameTimeNanos是帧的时间戳,只有到了时间的callback才能够被处理。

void doFrame(long frameTimeNanos, int frame) {       ......        long intendedFrameTimeNanos = frameTimeNanos;        startNanos = System.nanoTime();        //1.当前时间-帧时间表示延时的时间        final long jitterNanos = startNanos - frameTimeNanos;        if (jitterNanos >= mFrameIntervalNanos) {            final long skippedFrames = jitterNanos / mFrameIntervalNanos;            if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {                Log.i(TAG, "Skipped " + skippedFrames + " frames!  "                        + "The application may be doing too much work on its main thread.");            }      ......    try {        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);       //2.处理callback        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 {        AnimationUtils.unlockAnimationClock();        Trace.traceEnd(Trace.TRACE_TAG_VIEW);    }    ......}

然后我们再来看下之前传入的callback是啥,如下所示是CALLBACK_TRAVERSAL类型的,
而mTranversalRunnable是一个Runnable,最终会执行doTraversal函数,其中performTraversals就是真正开始界面的绘制。

void scheduleTraversals() {    //1.mTraversalScheduled为false才执行    if (!mTraversalScheduled) {        mTraversalScheduled = true;        //2.增加一个消息屏障        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();        //3.post了一个异步消息        mChoreographer.postCallback(                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);        if (!mUnbufferedInputDispatch) {            scheduleConsumeBatchedInput();        }        notifyRendererOfFramePending();        pokeDrawLockIfNeeded();    }}void doTraversal() {    if (mTraversalScheduled) {        mTraversalScheduled = false;        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);        if (mProfile) {            Debug.startMethodTracing("ViewAncestor");        }       //4.真正的绘制界面        performTraversals();        if (mProfile) {            Debug.stopMethodTracing();            mProfile = false;        }    }}

3.总结

绕来绕去是不是都有点儿晕了,其实弄懂之后流程也不是特别复杂,以下是流程图可以帮助理解
Android屏幕的刷新机制_第4张图片

更多相关文章

  1. 【android】适配多屏幕的最佳实践
  2. Android 手机屏幕那些事儿
  3. Android,关于设备独立像素(dip/dp),关于机型屏幕适配

随机推荐

  1. Android(安卓)2.0环境下的图标设计原则
  2. Android(安卓)实现密码键盘的相关知识点
  3. 实习第9天
  4. ANDROID XML图像资源文件详细讲解(一)
  5. Android多进程Process开发总结-优点与缺
  6. 基于android的网络音乐播放器-网络音乐的
  7. 蚕食鲸吞!iOS 与 Android(安卓)以 60% 市
  8. 技术迭代迷茫?Android资深架构师教你如何
  9. Android字符串进阶之一(特殊字符的输入)
  10. android 如何保护我们的app(一)(干货)