Androidandroid菜鸟笔记

编舞者Choreographer分析(上)

2019-10-16  本文已影响0人  李发糕

在Android中Choreographer起到调度器的作用。

首先看一下构造方法

private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;//处理回调的线程Looper
        mHandler = new FrameHandler(looper);//处理消息的Handler
        mDisplayEventReceiver = USE_VSYNC//是否使用VSYNC机制
                ? new FrameDisplayEventReceiver(looper, vsyncSource)
                : null;
        mLastFrameTimeNanos = Long.MIN_VALUE;//上一帧的渲染时间

        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());//一帧占用用时间 1s/帧率

        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];//初始化用于根据回调类型存储回调的队列的数组
        for (int i = 0; i <= CALLBACK_LAST; i++) {
            mCallbackQueues[i] = new CallbackQueue();//初始化回调队列
        }
        // b/68769804: For low FPS experiments.
        setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));//设置fps除数,降低fps 为了低fps实验 
}

在构造方法中需要传入Looper,在不同的线程中有不同的Choreographer持有其线程looper,通过threadLocal存储

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

下面简单的追踪一下绘制页面时编舞者在其中的作用。

当ViewRootImpl调用scheduleTraversals显示页面的时候,会通过mChoreographer.postCallback方法来调用mTraversalRunnable。

mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
public void postCallback(int callbackType, Runnable action, Object token) {
    postCallbackDelayed(callbackType, action, token, 0);
}

public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
    ···
    postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}

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) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
}

上面的方法做了两件事。

1 将回调Runnable也就是mTraversalRunnable 以及指定的回调调用时间根据回调类型存入相应的mCallbackQueues。

2 根据制定的回调时间 如果<=now : 立即安排本帧。否则通过发送延时message的方式等到到达指定的回调时间再安排此帧。在这里给msg设置为异步消息,防止被设置的handler屏障阻拦。设置what为MSG_DO_SCHEDULE_CALLBACK。我们可以先追踪看一下处理此msg的Handler:

private final class FrameHandler extends Handler {
    public FrameHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            ···
            case MSG_DO_SCHEDULE_CALLBACK://当接受的msg为MSG_DO_SCHEDULE_CALLBACK时,即上面代码发送的msg
                doScheduleCallback(msg.arg1);
                break;
        }
    }
}

void doScheduleCallback(int callbackType) {
        synchronized (mLock) {//添加内置锁
            if (!mFrameScheduled) {//当前帧没有进行schedule
                final long now = SystemClock.uptimeMillis();
                if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {//如果当前时间有回调
                    scheduleFrameLocked(now);//安排当前帧绘制 最后依然调用了此方法
                }
            }
        }
}

下面我们看一下这个安排帧绘制的方法scheduleFrameLocked

private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {//如果下一帧没有安排绘制
        mFrameScheduled = true;//设置标记,安排下一帧的绘制逻辑
        if (USE_VSYNC) {//是否使用垂直同步。关于此概念不了解的老哥可以自行搜索一下,网上有比较全面的解释。简单的说就是是否在统一的一个时间刷新页面。
           ···
            // If running on the Looper thread, then schedule the vsync immediately,
            // otherwise post a message to schedule the vsync from the UI thread
            // as soon as possible.
            if (isRunningOnLooperThreadLocked()) {
                scheduleVsyncLocked();
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtFrontOfQueue(msg);
            }
        } else {//不使用垂直同步
            final long nextFrameTime = Math.max(
                    mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
            ···
            Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, nextFrameTime);
        }
    }
}

上面的方法中,当开启了垂直同步时:

1 调用此方法的线程就是本Choreographer所在线程时:(为什么这么说呢,最后会解释)

​ 直接调用scheduleVsyncLocked方法安排垂直同步绘制此帧。

2 调用线程非本Choreographer线程:

​ 使用Handler发送MSG_DO_SCHEDULE_VSYNC的msg到队列头部,由本线程立即处理。

可以看一下handler的处理方式:

case MSG_DO_SCHEDULE_VSYNC:
    doScheduleVsync();
    break;
//
void doScheduleVsync() {
        synchronized (mLock) {
            if (mFrameScheduled) {
                scheduleVsyncLocked();
            }
        }
}

做了一些判断之后同样调用了scheduleVsyncLocked方法。

另一方面,当不需要进行垂直同步时:

首先计算绘制下一帧的时间,然后使用mHandler发送MSG_DO_FRAME的延时msg,在计算的绘制时间时处理。

handler的处理方式:

case MSG_DO_FRAME:
    doFrame(System.nanoTime(), 0);//直接调用doFrame方法。
    break;

而如何计算出下一帧的绘制时间呢?

final long nextFrameTime = Math.max(
                   mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);

为上一帧绘制时间 + sFrameDelay(默认10毫秒),如果小于now (当前时间),则取当前时间。

我们先看一下不需要垂直同步的情况:

在下一帧的绘制时间直接调用doFrame方法

void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        ···
        long intendedFrameTimeNanos = frameTimeNanos;//指定帧开始的时间
        startNanos = System.nanoTime();//实际的开始时间
        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.");
            }
            final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
            ···
            frameTimeNanos = startNanos - lastFrameOffset;//计算实际当前帧的开始时间
        }
                ···
        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);//记录当前帧信息
        mFrameScheduled = false;//修改标记为,此帧所回调的内容为到目前为止被添加进来的回调,如果再有新添加的回调就会再次调用scheduleFrameLocked内的方法通知onframe了
        mLastFrameTimeNanos = frameTimeNanos;//记录上一次frame渲染时间点
    }

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
            //locl
      //执行此帧所有的回调
        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_INPUT > CALLBACK_ANIMATION >

CALLBACK_TRAVERSAL > CALLBACK_COMMIT。

看一下doCallbacks方法

void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {
        if (!mFrameScheduled) {//当前是否有被安排绘制的帧
                return; // no work to do
            }
        // We use "now" to determine when callbacks become due because it's possible
        // for earlier processing phases in a frame to post callbacks that should run
        // in a following phase, such as an input event that causes an animation to start.
        final long now = System.nanoTime();
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);//取出需要在当前时间段运行的回调
        if (callbacks == null) {
            return;
        }
        mCallbacksRunning = true;
      
        if (callbackType == Choreographer.CALLBACK_COMMIT) {//如果是commmit类型,说明其他绘制回调已经执行完毕了,绘制此帧即将结束
            final long jitterNanos = now - frameTimeNanos;//计算前几种回调消耗的时间
            if (jitterNanos >= 2 * mFrameIntervalNanos) {//如果大于等于两帧的时间,因为下一帧绘制的开始时间可能已经安排好了,就会出现下一帧的开始时间早于此帧的结束时间。所以在这里修改其回调的结束时间。
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
                        + mFrameIntervalNanos;
                ···
                frameTimeNanos = now - lastFrameOffset;
                mLastFrameTimeNanos = frameTimeNanos;//设置结束时间
            }
        }
    }
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
          ···
            c.run(frameTimeNanos);//遍历相应的callback调用run方法
        }
    } finally {
        synchronized (mLock) {
            mCallbacksRunning = false;
            do {
                final CallbackRecord next = callbacks.next;
                recycleCallbackLocked(callbacks);//回收运行结束的callbackRecord
                callbacks = next;
            } while (callbacks != null);
        }
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

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

        public void run(long frameTimeNanos) {
            if (token == FRAME_CALLBACK_TOKEN) {//自定义的帧回调 会调用用户自己实现的onframe
                ((FrameCallback)action).doFrame(frameTimeNanos);
            } else {
                ((Runnable)action).run();//这里就是一般的情况,在token!=FRAME_CALLBACK_TOKEN 时,直接调用回调。
            }
        }
}

到此为止,未开启垂直同步情况下的编舞者逻辑就都看完了。

简单上一下流程图,画的有点难看,大致意思差不多

未命名文件.jpg

未开启垂直同步的情况到这里就结束了,下一篇我们分析一下垂直同步的情况~~

上一篇下一篇

猜你喜欢

热点阅读