Android屏幕的刷新机制

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

1.屏幕刷新机制

1)屏幕显示

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

2)VSync信号

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

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.总结

绕来绕去是不是都有点儿晕了,其实弄懂之后流程也不是特别复杂,以下是流程图可以帮助理解
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值