Android从屏幕刷新到View的绘制(二)之Choreographer、Vsync与屏幕刷新

363 阅读9分钟

0.相关分享:

Android从屏幕刷新到View的绘制(一)之 Window、WindowManager和WindowManagerService之间的关系

Android从屏幕刷新到View的绘制(二)之Choreographer、Vsync与屏幕刷新

1. 相关类

Choreographer 编舞者

Android4.1之后新增的机制,用于配合系统的 Vsync (Vertical Synchronization,垂直同步)信号,编舞者可以接受系统的 Vsync信号,统一管理引用的输入、动画、绘制等任务的执行时机,使得整体显得协调连贯。业界一般通过它来监控应用的帧率。

CallbackQueue

Choreographer添加的任务最后都被封装为CallbackRecord,以链表的形式保存在CallbackQueue中,Choreographer的mCallbackQueues是一个数组,长度为4,保存着五个CallbackQueue链表。分别处理:输入、动画、遍历绘制等任务。

FrameDisplayEventReceiver

Android4.1之后,默认开启了Vsync功能,FrameDisplayEventReceiver用于接收Vsync事件,并将事件提交给looper(一般为主线程)。它会通过JNI创建一个IDisplayEventConnection的Vsync监听者。

FrameHandler

处理主线程中关于Vsync的事件。执行异步消息,有延迟的任务发延迟消息、不在原线程的发到原线程。

2. 源码分析

在前面的分析我们知道,Activity的onResume()执行完之后,会通知WMS进行window的添加。过程中会调用到ViewRootImpl的setView(),最后又会走到scheduleTraversals()方法。

另外,其实我们使用View.invalidate()、requestLayout()等方法时,最后都会走到ViewRootImpl的scheduleTraversals()方法。也就是说,所有UI的变化最后都会走到ViewRootImpl的scheduleTraversals()方法

在Android的framework源码阅读过程在,我们会遇到很多类如一一对应的 scheduleXXX() 与 performXXX(),这可以理解为“发起请求”以及得到响应之后的“执行任务”。在这里,scheduleTraversals() 就是进行发起绘制请求,等到 Vsync 信号到来之后,执行绘制performTraversals()。

先来挂一张流程图,可能会一脸懵,但看完后续源码解析后,这张流程图就一目了然了:

请添加图片描述

2.1 Choreographer的创建

在ViewRootImpl对象创建的时候,会获取到一个Choreographer实例:

//ViewRootImpl
Choreographer mChoreographer;
//ViewRootImpl实例是在添加window时创建的
public ViewRootImpl(Context context,Display display){
    //...
    mChoreographer = Choreographer.getInstance();
    //...
}

Choreographer维护了一个looper和handler:

//Choreograper
//通过ThreadLocal获取线程私有的Choreographer
private static final ThreadLocal<Choreographer> sThreadInstance =
    new ThreadLocal<Choreographer>() {
    @Override
    protected Choreographer initialValue() {
        //当前线程的Looper
        Looper looper = Looper.myLooper();
        if (looper == null) {
            throw new IllegalStateException("The current thread must have a looper!");
        }
        return new Choreographer(looper, VSYNC_SOURCE_APP);
    }
};

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

我们来看到Choreographer的构造方法,它做了:

  1. 维护了所在线程的Looper
  2. 为该looper设置了一个FrameHandler,处理绘制消息
  3. Android4.1之后,还有一个Vsync监听者 FrameDisplayEventReceiver()
  4. 初始化了不同事件的回调队列
private Choreographer(Looper looper, int vsyncSource) {
    //所在线程的looper
    mLooper = looper;
    //handler消息处理,只能收到当前线程Looper的消息
    mHandler = new FrameHandler(looper);
    //Vsync消息监听者,Android4.1之后默认开启USE_VSYNC
    mDisplayEventReceiver = USE_VSYNC
        ? new FrameDisplayEventReceiver(looper, vsyncSource)
        : null;
    // 计算一帧的时间,Android手机屏幕是60Hz的刷新频率,就是16ms
    mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());

    //数组长度为4 (在android8.0中)
    mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
    for (int i = 0; i <= CALLBACK_LAST; i++) {
        mCallbackQueues[i] = new CallbackQueue();
    }
}

我们可以按经验来看着几个对象,猜测当Vsync信号到来时,可以进行刷新,FrameHandler收到了这个消息,回调给CallbackQueue中的各个View任务。

2.2 向Choreographer添加任务

mChoreographer.postCallback()就是向CallbackQueue中添加View任务的方法,这个方法有三个参数,第一个参数有四种类型(Android8中是四种类型),表示的是这个任务的类型:

//Choreographer
//输入事件,优先级最高
public static final int CALLBACK_INPUT = 0;
//动画任务
public static final int CALLBACK_ANIMATION = 1;
//绘制任务
public static final int CALLBACK_TRAVERSAL = 2;
//提交,优先级最低,最后执行
public static final int CALLBACK_COMMIT = 3;

我们之前谈到,所有UI的变化都走到了ViewRootImpl的scheduleTraversals()中,这个方法向Choreographer添加了任务申请:

//ViewRootImpl
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        //标志位防重复提交,也就是多次requestLayout、invalidate在一次绘制申请中,只会生效一次
        mTraversalScheduled = true;
        //添加同步屏障,挡住后续所有的同步消息,让Looper优先分发异步事件。Vsync来到时,发布的是异步消息,优先处理
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //添加任务
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

来到scheduleTraversals(),核心做了几件事,这几件事都很重要,和后续有紧密联系:

  1. 将申请标志位置为true,避免重复提交,在一次绘制申请中,多次requestLayout/invalidate只会生效一次 我们知道invalidate等方法都只是一个申请,方法内没有数据变化,在Vsync到来之前,View的数据可以变化,最终只会生效最后的情况,所以没必要重复接收多个invalidate()请求,就好像你对妈妈说:“我饿了,我想吃一碗饭”,妈妈收到了,说“好的”,妈妈就去煮饭了,你又说“我饿了,我想吃两碗饭”,妈妈不会理会你,反正最后你吃几碗是几碗,妈妈直到你饿了已经去煮了
  2. 向Looper的MessageQueue中添加同步屏障,保证Vsync来到后异步消息能够立即执行。
  3. 向Choreographer添加mTraversalRunnable,注册绘制任务的callback

我们来看一下这个mTraversalRunnable具体做了什么来执行绘制:

//ViewRootImpl.java
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        //只做了一件时,调用外部类,也就是ViewRootImpl对象的doTraversal()方法
        doTraversal();
    }
}
//ViewRootImpl只管绘制,所以TraversalRunnable订好了只做doTraversal()
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
    if (mTraversalScheduled) {
        //标志设为false,可以继续接受scheduleTraversal()任务
        mTraversalScheduled = false;
        //取出Looper的MessageQueue中的同步屏障,恢复同步消息的分发
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        //进入ViewRootImpl下的DecorView的绘制流程
        performTraversals();
    }
}

这里有几个点注意:

  1. mTraversalScheduled字段又被设置为false,和之前的故事相照应:妈妈把电饭煲端出来了,你可以吃饭了,怎么吃是你的事了,妈妈很疼你,如果你还要吃,允许你再请妈妈煮饭
  2. 之前由于添加了同步屏障,为了保证Vsync来到后立即绘制,但屏蔽了正常的同步消息,所以这里需要把同步屏障取消掉
  3. Vsync到来之后,由于之前注册的回调,所以会执行TraversalRunnable->run()->ViewRootImpl.doTraversal()->performTraversals()->View的绘制流程。

那么这个回调在注册过程中发生了什么呢?

mChoreographer.postCallback()内部调用了postCallbackDelayed(),紧接着调用了postCallbackDelayedInternal():

//Choreographer
//这里的action,就是传入的Runnable,由于类型不一定,所以改成了Object
private void postCallbackDelayedInternal(int callbackType,
                                         Object action, Object token, long delayMillis) {
    
    synchronized (mLock) {
        //当前时间
        final long now = SystemClock.uptimeMillis();
        //执行时间
        final long dueTime = now + delayMillis;
        //将传入的action包装在CallbackRecord中,插入到队列里
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
		//如果时间已经到了,其实就是delayMillis==0的情况
        if (dueTime <= now) {
            //立即执行scheduleFrameLocked()
            scheduleFrameLocked(now);
        } else {
            //如果这个请求时延迟任务,那么就将其设置为异步任务,并添加上时间,放在Message中,发送Message。
            //复用了 MSG_DO_SCHEDULE_CALLBACK的message,将action传入
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}

这里主要做了几件事:

  1. 计算时间,如果需要立即执行,则调用scheduleFrameLocked()
  2. 如果是延迟消息,先去复用池获取一个msg,将action传入,这里的action是之前传入的mTraversalRunnable
  3. 由于这是绘制任务,为了通过同步屏障,我们需要设置消息为异步消息 msg.setAsynchronous(true)
  4. message需要定一个int what让handler来决定处理方式

CallbackQueue.addCallbackLocked()就是把dueTime、action、token(这里的token为null)等信息存入CallbackRecord后,放在CallbackQueue链表中。

我们先来看一下直接发送的 scheduleFrameLocked()

//Choreographer
private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {
        //似乎成了一个约定,只要是schedule申请,就会有一个标志来表示是否已接受过这个申请。
        mFrameScheduled = true;
        if (USE_VSYNC) {//Android4.1之后默认开启Vsync垂直同步
            //线程判断
            if (isRunningOnLooperThreadLocked()) {
                //如果就是本线程发起的,就进入这里
                scheduleVsyncLocked();
            } else {
                //如果是其他线程调用的这个方法,则通过Message来将任务的线程切换到本线程执行。最终还是执行scheduleVsyncLocked()
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtFrontOfQueue(msg);
            }
        } else {
            //没有垂直同步,就直接doFrame()
            Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, nextFrameTime);
        }
    }
}

如果开启了Vsync,那么进入到scheduleVsyncLocked():

//Choreographer
private void scheduleVsyncLocked() {
    //调用了DisplayEventReceiver的方法
    mDisplayEventReceiver.scheduleVsync();
}
//DisplayEventReceiver
public void scheduleVsync() {
    //调用了本地方法
    nativeScheduleVsync(mReceiverPtr);

}
//这是一个本地方法
private static native void nativeScheduleVsync(long receiverPtr);

我们之前介绍直到,DisplayEventReceiver它会通过JNI创建一个IDisplayEventConnection的Vsync监听者。我们来到framework/base/core/jni包下看看:

//android_view_DisplayEventReceiver.cpp
static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
    sp<NativeDisplayEventReceiver> receiver =
        reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
    status_t status = receiver->scheduleVsync();
    if (status) {
        String8 message;
        message.appendFormat("Failed to schedule next vertical sync pulse.  status=%d", status);
        jniThrowRuntimeException(env, message.string());
    }
}

NativeDisplayEventReceiver继承自DisplayEventDispatcher

status_t DisplayEventDispatcher::scheduleVsync() {
    if (!mWaitingForVsync) {
        //...
        //又由Dispatcher让DisplayEventReceiver去requestNextVsync()
        status_t status = mReceiver.requestNextVsync();
        //...
        mWaitingForVsync = true;
    }
    return OK;
}

顾名思义,让DisplayEventReceiver去申请下一个Vsync,最后来到/gui/DisplayEventReceiver.cpp:

//gui/DisplayEventReceiver.cpp
status_t DisplayEventReceiver::requestNextVsync() {
    if (mEventConnection != NULL) {
        mEventConnection->requestNextVsync();
        return NO_ERROR;
    }
    return NO_INIT;
}

我们可以看到,DisplayEventReceiver通过IDisplayEventConnection对象去申请下一个Vsync

class BpDisplayEventConnection : public SafeBpInterface<IDisplayEventConnection> {
    void requestNextVsync() override {
  	 	callRemoteAsync<decltype(&IDisplayEventConnection::requestNextVsync)>(
            Tag::REQUEST_NEXT_VSYNC);
    }
}

看名字就知道,这个DisplayEventConnection只是一个binder引用,需要远程调用其实体去requestNextVsync,我们来看到BnDisplayEventConnection::onTransact是如何处理这个事务的:

status_t BnDisplayEventConnection::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
                                              uint32_t flags) {
    if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast<uint32_t>(Tag::LAST)) {
        return BBinder::onTransact(code, data, reply, flags);
    }
    auto tag = static_cast<Tag>(code);
    switch (tag) {
        case Tag::STEAL_RECEIVE_CHANNEL:
            return callLocal(data, reply, &IDisplayEventConnection::stealReceiveChannel);
        case Tag::SET_VSYNC_RATE:
            return callLocal(data, reply, &IDisplayEventConnection::setVsyncRate);
        case Tag::REQUEST_NEXT_VSYNC:
            //通过callLocalAsync()来request_next_vsync
            return callLocalAsync(data, reply, &IDisplayEventConnection::requestNextVsync);
    }
}

接下来来到SafeInterface.h中的callLocalAsync():

//SafeInterface.h
template <typename Method>
status_t callLocalAsync(const Parcel& data, Parcel* /*reply*/, Method method) {
   
    using ParamTuple = typename SafeInterface::ParamExtractor<Method>::ParamTuple;
    typename RawConverter<std::tuple<>, ParamTuple>::type rawArgs{};

    //发送parcel数据
    status_t error = InputReader<ParamTuple>{mLogTag}.readInputs(data, &rawArgs);
    
    //方法返回
    MethodCaller<ParamTuple>::callVoid(this, method, &rawArgs);
	
    return NO_ERROR;
}

我只跟到这里,这里应该只是发送Vsync通知SurfaceFlinger刷新的信号,下面两篇文章对Vsync做了更深层的底层探究

这有一个native层Vsync分发的文章: blog.csdn.net/Android0620…

推荐一个SurfaceFlinger的学习文章 : www.jianshu.com/p/c954bcceb…

本文只关注到,最后Vsync信号会被发送给DisplayEventReceiver::AttachedEvent::handleEvent():

//displayservice/DisplayEventReceiver.cpp
int DisplayEventReceiver::AttachedEvent::handleEvent(int fd, int events, void* /* data */) {
    
    constexpr size_t SIZE = 1;

    ssize_t n;
    FwkReceiver::Event buf[SIZE];
    while ((n = mFwkReceiver.getEvents(buf, SIZE)) > 0) {
        for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
            const FwkReceiver::Event &event = buf[i];

            uint32_t type = event.header.type;
            uint64_t timestamp = event.header.timestamp;

            switch(buf[i].header.type) {
                case FwkReceiver::DISPLAY_EVENT_VSYNC: {
                    //回调onVsync()方法
                    mCallback->onVsync(timestamp, event.vsync.count);
                } break;
                case FwkReceiver::DISPLAY_EVENT_HOTPLUG: {
                    mCallback->onHotplug(timestamp, event.hotplug.connected);
                } break;
                default: {
                    LOG(ERROR) << "AttachedEvent handleEvent unknown type: " << type;
                }
            }
        }
    }

    return 1; // keep on going
}

最终回调到Java层的DisplayEventReceiver.onVsync(),跟了这么深,还记得DisplayEventReceiver在java层的实现类是谁么?真棒,是Choreographer中的内部类FrameDisplayEventReceiver,我们来看看它:

//Choreographer
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) {
        
        if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
            scheduleVsync();
            return;
        }

        long now = System.nanoTime();
        if (timestampNanos > now) {
            timestampNanos = now;
        }

        if (mHavePendingVsync) {
            
        } else {
            mHavePendingVsync = true;
        }

        mTimestampNanos = timestampNanos;
        mFrame = frame;
        //将本身作为runnable传入msg,被调用执行后,会进入run()->doFrame()
        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);
    }
}

onVsync()方法中,将接收器本身作为Runnable传入异步消息msg,可以越过同步屏障,优先执行,但它并不一定立即执行,仅仅是越过同步屏障罢了,如果前面有较为耗时的操作,仍然需要等待,然后才会执行本次doFrame()。这也说明主线程不应当有耗时操作,否则就会影响屏幕输入事件、绘制等任务,出现ANR问题。

最后来到 doFrame() 我们来看看具体是如何逐步调用各种事件响应的:

//Choreographer
void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        if (!mFrameScheduled) {
            return; // no work to do
        }

        //...
        // 预期执行时间
        long intendedFrameTimeNanos = frameTimeNanos;
        startNanos = System.nanoTime();
        // 超时时间是否超过一帧的时间(这是因为MessageQueue虽然添加了同步屏障,但是还是有正在执行的同步任务,导致doFrame延迟执行了)
        final long jitterNanos = startNanos - frameTimeNanos;
        if (jitterNanos >= mFrameIntervalNanos) {
            // 计算掉帧数
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                // 掉帧超过30帧打印Log提示
                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;
        }
        //...
        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
        // Frame标志位恢复
        mFrameScheduled = false;
        // 记录最后一帧时间
        mLastFrameTimeNanos = frameTimeNanos;
    }

    try {
        // 按类型顺序 执行任务
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
		//首先处理输入事件
        mFrameInfo.markInputHandlingStart();
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
		//然后执行动画
        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
		//接下来执行View的绘制
        mFrameInfo.markPerformTraversalsStart();
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
		//最后提交
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } 
    //...
}

接下来就到doCallbacks()把各个CallbackQueue中的任务回调执行,还记得吗?最初我们会提交任务到Choreographer,然后通过postCallback来等待回调,现在就是回调的时机了:

//Choreorapher
void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {
        //执行其中时间ok的任务,也就是查找到达执行时间的CallbackRecord
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
            now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }
        mCallbacksRunning = true;
		//一些log。。。
    }
    try {
        //遍历执行CallbackRecord的run()方法,进一步调用里面的action的方法。例如传入的比如mTraversalRunnable的run()方法
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
            //内部回调callback的run
            c.run(frameTimeNanos);
        }
    } finally {
        synchronized (mLock) {
            mCallbacksRunning = false;
            do {
                final CallbackRecord next = callbacks.next;
                //回收这个CallbackRecord
                recycleCallbackLocked(callbacks);
                callbacks = next;
            } while (callbacks != null);
        }
    }
}

我们最后再来看一下CallbackRecord这个类:

private static final class CallbackRecord {
    public CallbackRecord next;
    public long dueTime;
    public Object action; // Runnable or FrameCallback
    public Object token;

    @UnsupportedAppUsage
    public void run(long frameTimeNanos) {
        if (token == FRAME_CALLBACK_TOKEN) {
            // 通过postFrameCallback 或 postFrameCallbackDelayed,会执行这里
            ((FrameCallback)action).doFrame(frameTimeNanos);
        } else {
            //取出Runnable执行run()
            ((Runnable)action).run();
        }
    }
}

例如ViewRootImpl发起的绘制任务时传递token为null,所以进入到了mTraversalRunnable.run()。接下去就是我们熟悉的去除内存屏障,performTraversals()->View的绘制流程。

此外,我们注意到token还有可能是 FRAME_CALLBACK_TOKEN,这一般被用来计算丢帧情况。通过Choreographer.postFrameCallback()。

我们最后再来回顾流程图,应当是一目了然了:

请添加图片描述

  1. ViewRootImpl可以接受来自View的invalidate、requestLayout等请求

  2. ViewRootImpl通过scheduleTraversals()发起请求,向Choreographer编舞者添加绘制回调

  3. Choreographer通过FrameDisplayEventReceiver来注册接受Vsync信号。这是个DisplayEventReceiver的binder引用,是个BpBinder。通过Binder通信,让BnDisplayEventReceiver去接受Vsync信号。

  4. 可以注意到,虽然屏幕16ms刷新,但如果屏幕布局元素一直没有变化,那么就不会申请重绘,也就一直显示当前的页面。我们平时屏幕大部分时间还是静态的,所以不会那么高频地去进行刷新。除非我们在看电影、打游戏等需要持续变化的,才会高频地刷新屏幕。因为需要不断重绘元素或者画面。(此外,这里我做了一个测试,在动画过程中,执行耗时操作,并不会报ANR,只会跳帧/丢帧?)

    Choreographer: Skipped 599 frames!  The application may be doing too much work on its main thread.
    

    其实也是因为ANR只发生在:

    • Service Timeout:前台服务在20s内未执行完成
    • BroadcastQueue Timeout:前台广播在10s内未执行完成
    • ContentProvider Timeout:内容提供者在publish过超时10s;
    • InputDispatching Timeout:输入事件分发超时5s,包括按键和触摸事件。

    对于重绘、动画任务被主线程耗时操作堵住时不会报ANR,也是因为这个这两个事件超时执行了并不会报错,仅是在Choreographer中做了个JANK记录,打印出来。

    //Choreographer
    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.");
        }
        final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
        if (DEBUG_JANK) {
            Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                  + "which is more than the frame interval of "
                  + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                  + "Skipping " + skippedFrames + " frames and setting frame "
                  + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
        }
        frameTimeNanos = startNanos - lastFrameOffset;
    

    但输入任务是有定时炸弹的,可以看到这篇文章:

    img

  5. 后续就进入到了View的绘制流程。具体如何绘制的,如何通过局部重绘来优化性能的,我们在View的绘制中探讨。

参考文献

blog.csdn.net/qq_34519487… www.jianshu.com/p/86d00bbda… blog.csdn.net/Android0620…