UI 优化系列专题,来聊一聊 Android 渲染相关知识,主要涉及 UI 渲染背景知识如何优化 UI 渲染两部分内容。


UI 优化系列专题
  • UI 渲染背景知识

《View 绘制流程之 setContentView() 到底做了什么?》
《View 绘制流程之 DecorView 添加至窗口的过程》
《深入 Activity 三部曲(3)View 绘制流程》
《Android 之 LayoutInflater 全面解析》
《关于渲染,你需要了解什么?》
《Android 之 Choreographer 详细分析》

  • 如何优化 UI 渲染

《Android 之如何优化 UI 渲染(上)》
《Android 之如何优化 UI 渲染(下)》


Android 的 UI 渲染性能是 Google 长期以来非常重视的,基本每次 Google I/O 都会花很多篇幅讲这一块。不过随着 Android 系统的不断演进和完善,时至今日,关于 Android UI 卡顿的话题也越来越少。

Google 在 2012 年的 I/O 大会上宣布了 Project Butter 计划,那个曾经严重影响 Android 口碑的 UI 流程性问题,首先在这得到有效的控制。并且在 Android 4.1 中正式开启了这个机制。

Project Butter 主要包含三个组成部分:VSYNC、Triple Buffering 和今天要重点分析的 Choreographer。关于 Project Butter 的详细介绍,你可以参考这里。

Choreographer

Choreographer 是 Android 4.1 新增的机制,用于配合系统的 VSYNC 中断信号。它本质是一个 Java 类,如果直译的话为舞蹈指导,看到这个词不得不赞叹设计者除了 Coding 之外的广泛视野。舞蹈是有节奏的,节奏使舞蹈的每个动作更加协调和连贯;视图刷新也是如此,Choreographer 可以接收系统的 VSYNC 信号,统一管理应用的输入、动画和绘制等任务的执行时机。业界一般通过它来监控应用的帧率。

我们先从 Choreographer 的构造方法入手,看看 Choreographer 是如何协调任务的执行。

private Choreographer(Looper looper, int vsyncSource) {        // 当前线程的Looper        mLooper = looper;        // 使用该Looper创建FrameHandler        mHandler = new FrameHandler(looper);        // 是否开启VSYNC,开启VSYNC后将通过FrameDisplayEventReceiver接收VSYNC脉冲信号        mDisplayEventReceiver = USE_VSYNC                ? new FrameDisplayEventReceiver(looper, vsyncSource)                : null;        mLastFrameTimeNanos = Long.MIN_VALUE;        // 计算一帧的时间        // Android手机屏幕采用60Hz的刷新频率        // 这里是纳秒 ≈16000000ns 还是16ms        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());        // 创建一个CallbackQueu的数组,默认为4        // CallbackQueue中存放要执行的输入、动画、遍历绘制等任务        // 也就是 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL        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)); }
  1. Choreographer 的构造方法被设计成私有,并且是线程单例的。只能通过其内部的 getInstance 方法获取当前线程的 Choreographer 实例:
public static Choreographer getInstance() {    // Choreographer线程单例的实现方式    return sThreadInstance.get();}

通过 ThreadLocal 实现 Choreographer 的线程单例。

private static final ThreadLocal sThreadInstance =            new ThreadLocal() {        @Override        protected Choreographer initialValue() {            // 获取当前线程的Looper对象            Looper looper = Looper.myLooper();            if (looper == null) {                // 如果当前线程未创建Looper对象则抛出异常                // 主线程(UI线程)的Looper默认在ActivityThread的main方法被创建                throw new IllegalStateException("The current thread must have a looper!");            }            // 为当前线程创建一个Choreographer对象            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);            if (looper == Looper.getMainLooper()) {                // 如果是UI线程赋值给成员mMainInstance                mMainInstance = choreographer;            }            return choreographer;        }};
  1. Choreographer 的构造必须传递一个 Looper 对象,其内部会根据该 Looper 创建一个 FrameHandler。Choreographer 的所有任务最终都会发送到该 Looper 所在的线程。
private final class FrameHandler extends Handler {                public FrameHandler(Looper looper) {            super(looper);        }        @Override        public void handleMessage(Message msg) {            switch (msg.what) {                case MSG_DO_FRAME:                    // 执行doFrame                    // 如果启用VSYNC机制,当VSYNC信号到来时触发                    doFrame(System.nanoTime(), 0);                    break;                case MSG_DO_SCHEDULE_VSYNC:                    // 申请VSYNC信号,例如当前需要绘制任务时                    doScheduleVsync();                    break;                case MSG_DO_SCHEDULE_CALLBACK:                    // 需要延迟的任务,最终还是执行上述两个事件                    doScheduleCallback(msg.arg1);                    break;            }        }}
  1. 注意 USE_VSYNC,用于判断当前是否启用 VSYNC 机制,Android 在 4.1 之后默认开启该机制。
private static final boolean USE_VSYNC = SystemProperties.getBoolean(            "debug.choreographer.vsync", true);

FrameDisplayEventReceiver 是 DisplayEventReceiver 的子类,DisplayEventReceiver 是一个 abstract class。在 DisplayEventReceiver 的构造方法会通过 JNI 创建一个 IDisplayEventConnection 的 VSYNC 的监听者。

public DisplayEventReceiver(Looper looper, int vsyncSource) {        if (looper == null) {            throw new IllegalArgumentException("looper must not be null");        }        mMessageQueue = looper.getQueue();        // 注册VSYNC信号监听者        mReceiverPtr = nativeInit(new WeakReference(this), mMessageQueue,                vsyncSource);        mCloseGuard.open("dispose");    }

另外 DisplayEventReceiver 内还包括用于申请 VSYNC 信号的 scheduledVsync 方法,

public void scheduleVsync() {        if (mReceiverPtr == 0) {            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "                    + "receiver has already been disposed.");        } else {            // 申请VSYNC中断信号            // 会回调onVsync方法            nativeScheduleVsync(mReceiverPtr);        }}

和用于接收 VSYNC 信号的 onVsync 方法。这样,当应用需要绘制时,通过 scheduledVsync 方法申请 VSYNC 中断,来自 EventThread 的 VSYNC 信号就可以传递到 Choreographer:

public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {        // 该方法在其子类FrameDisplayEventReceiver中被重写        // 目的是通知Choreographer}
  1. CallbackQueue,用于保存通过 postCallback 添加的任务。目前一共定义了四种任务类型,它们分别是:
  • CALLBACK_INPUT:优先级最高,和输入事件处理有关。
  • CALLBACK_ANIMATION:优先级其次,和 Animation 的处理有关
  • CALLBACK_TRAVERSAL:优先级最低,和 UI 绘制任务有关
  • CALLBACK_COMMIT:最后执行,和提交任务有关(在 API Level 23 添加)

优先级的高低和处理顺序有关,每当收到 VSYNC 信号时,Choreographer 将首先处理 INPUT 类型的任务,然后是 ANIMATION 类型,最后才是 TRAVERSAL 类型。

通过 Choreographer 添加的任务最后都被封装成 CallbackRecord,同种任务之间按照时间顺序以链表的形式保存在 CallbackQueue 内。

private static final class CallbackRecord {        // 链表,指向下一个        public CallbackRecord next;        // 到期时间        public long dueTime;        // Runnable or FrameCallback        public Object action;        public Object token;        public void run(long frameTimeNanos) {            if (token == FRAME_CALLBACK_TOKEN) {                // 通过postFrameCallback 或 postFrameCallbackDelayed                // 会执行这里                ((FrameCallback)action).doFrame(frameTimeNanos);            } else {                ((Runnable)action).run();            }        } }

CallbackQueue 是一个容量为 4 的数组,分别对应不同的任务类型。


接下来,以 View 的绘制流程为例,从 ViewRootImpl 的 scheduleTraversals 方法开始,其内部通过 Choreographer 的 postCallback 将绘制任务添加到 Chorographer。关于 View 绘制流程的详细分析,可以参考《View 绘制流程之 DecorView 添加至窗口的过程》和《深入 Activity 三部曲(3)之 View 绘制流程》。

void scheduleTraversals() {        if (!mTraversalScheduled) {            mTraversalScheduled = true;            // 同步屏障,阻塞所有的同步消息            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();            // 注意mTraversaRunnable是一个Runnable对象            // 通过 Choreographer 发送绘制任务            mChoreographer.postCallback(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);           // ...        }    }

Choreographer 是线程单例的,大家是否还记得 Android 系统的 Looper 对象也是 线程单例。主线程 Looper 是在 ActivityThread 的 main 方法被创建。如果要在子线程使用 Handler,必须先为其创建一个 Looper 实例。

Choreographer 提供了两种添加任务的方式,postCallback() 和 postFrameCallback(),当然还有对应的 delay 方法。

  • postCallback 对应调用 postCallbackDelayed
public void postCallbackDelayed(int callbackType,            Runnable action, Object token, long delayMillis) {        if (action == null) {            throw new IllegalArgumentException("action must not be null");        }        if (callbackType < 0 || callbackType > CALLBACK_LAST) {            throw new IllegalArgumentException("callbackType is invalid");        }        // 最终都会调用到postCallbackDelayedInternal        postCallbackDelayedInternal(callbackType, action, token, delayMillis);}
  • postFrameCallback 对应调用 postFrameCallbackDelayed
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {        if (callback == null) {            throw new IllegalArgumentException("callback must not be null");        }        //最终调用postCallbackDelayedInternal        postCallbackDelayedInternal(CALLBACK_ANIMATION,                callback, FRAME_CALLBACK_TOKEN, delayMillis); }

postCallback 相比 postFrameCallback 更加灵活一些。

它们最终都会调用到 postCallbackDelayedInternal 方法:

private void postCallbackDelayedInternal(int callbackType,            Object action, Object token, long delayMillis) {        synchronized (mLock) {            // 当前时间            final long now = SystemClock.uptimeMillis();            // 加上延迟时间            final long dueTime = now + delayMillis;            // 根据任务类型添加到mCallbackQueues中            // VSYNC信号处理任务具有优先级            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);            if (dueTime <= now) {                //表示立即执行,立即申请VSYNC信号                scheduleFrameLocked(now);            } else {                // 在指定时间运行,最终仍然会调用scheduleFrameLocked                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);                // 到时根据callbackType在mCallbackQueues中查找执行                msg.arg1 = callbackType;                // 消息设置为异步                msg.setAsynchronous(true);                mHandler.sendMessageAtTime(msg, dueTime);            }        }}

根据任务类型 callbackType 添加到对应的 CallbackQueue 内,然后判断任务是否有延迟,无延迟则立即执行 scheduleFrameLocked 方法,否则发送定时消息到 FrameHandler,不过其最终还是调用到 scheduleFrameLocked 方法:

private void scheduleFrameLocked(long now) {        //mFrameScheduled默认为false        if (!mFrameScheduled) {            mFrameScheduled = true;            // 判断是否开启VSYNC            if (USE_VSYNC) {                // 判断是否在原线程                if (isRunningOnLooperThreadLocked()) {                    //默认会走这里                    scheduleVsyncLocked();                } else {                    // 否则不在原线程,发送消息到原线程                    // 最后还是调用scheduleVsyncLocked方法                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);                    msg.setAsynchronous(true);                    mHandler.sendMessageAtFrontOfQueue(msg);                }            } else {                // 如果未开启VSYNC则直接doFrame方法                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);            }        }}

注意 USE_VSYNC,如果系统未开启 VSYNC 机制,此时直接发送 MSG_DO_FRAME 消息到 FrameHandler。注意查看上面贴出的 FrameHandler 代码,此时直接执行 doFrame 方法。

不过 Android 4.1 之后系统默认开启 VSYNC,还记得在 Choreographer 的构造方法会创建一个 FrameDisplayEventReceiver,scheduleVsyncLocked 方法将会通过它申请 VSYNC 信号。

  • 这里注意 isRunningOnLooperThreadLocked 方法,其内部根据 Looper 判断是否在原线程,否则发送消息到 FrameHandler。最终还是会调用 scheduleVsyncLocked 方法申请 VSYNC 信号。

通过 FrameDisplayEventReceiver 申请 VSYNC 信号的过程如下:

private void scheduleVsyncLocked() {    // 调用 FrameDisplayEventReceiver 的scheduleVsync   // 实际调用到其父类DisplayEventReceiver    mDisplayEventReceiver.scheduleVsync();}

前面我们也有说过,申请 VSYNC 信号实际是在其父类 DisplayEventReceiver。

public void scheduleVsync() {        if (mReceiverPtr == 0) {            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "                    + "receiver has already been disposed.");        } else {            // 申请VSYNC信号            nativeScheduleVsync(mReceiverPtr);        }}

接着看下 VSYNC 信号的接收方法 onVsync,该方法在其子类 FrameDisplayEventReceiver 中重写:

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) {                              // 忽略来自非主屏的VSYNC信号                scheduleVsync();                return;            }             // ... 省略            if (mHavePendingVsync) {                Log.w(TAG, "Already have a pending vsync event.  There should only be "                        + "one at a time.");            } else {                mHavePendingVsync = true;            }            mTimestampNanos = timestampNanos;            mFrame = frame;            // 发送消息执行doFrame            // 注意this,表示当前Runnable            Message msg = Message.obtain(mHandler, this);            msg.setAsynchronous(true);            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);        }        @Override        public void run() {            mHavePendingVsync = false;            // 回调这里,执行doFrame方法            doFrame(mTimestampNanos, mFrame);        }}

FrameDisplayEventReceiver 实现了 Runnable,将其作为 callback 发送到 FrameHandler,此时 run 方法便得到执行并且执行 doFrame 方法:

void doFrame(long frameTimeNanos, int frame) {        final long startNanos;        synchronized (mLock) {            if (!mFrameScheduled) {                // 不是在执行Frame任务直接return                return;            }            // ... 省略            // 预期执行时间            long intendedFrameTimeNanos = frameTimeNanos;            // 当前时间            startNanos = System.nanoTime();            final long jitterNanos = startNanos - frameTimeNanos;            // 超时时间是否超过一帧的时间            if (jitterNanos >= mFrameIntervalNanos) {                // 计算掉帧数                final long skippedFrames = jitterNanos / mFrameIntervalNanos;                // 掉帧超过30帧打印Log提示                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {                    // 著名的掉帧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;            }            if (frameTimeNanos < mLastFrameTimeNanos) {                // 未知原因,居然小于最后一帧的时间                // 重新申请VSYNC信号                scheduleVsyncLocked();                return;            }            if (mFPSDivisor > 1) {                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {                    scheduleVsyncLocked();                    return;                }            }            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();            // 先执行CALLBACK_INPUT任务            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);            mFrameInfo.markAnimationsStart();            // 再执行CALLBACK_ANIMATION            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);            mFrameInfo.markPerformTraversalsStart();            // 其次执行CALLBACK_TRAVERSAL            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);            // API Level 23 之后加入,            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);        } finally {            AnimationUtils.unlockAnimationClock();            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }}
  1. 注意第一个 if 语句,不知道大家是否在自己项目的 Logcat 台遇到过这样一条日志:
Skipped (该值>=30) frames!  The application may be doing too much work on its main thread

该 Log 用于提示开发人员当前存在耗时的任务导致 UI 绘制掉帧超过 30 帧(≈ 16ms * 30 >= 480ms)。

  1. 注意看方法的最后,按照类型顺序触发 doCallbacks 回调相关任务。
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);

doCallbacks 方法将根据不同的任务类型依次执行其 run 方法:

void doCallbacks(int callbackType, long frameTimeNanos) {        CallbackRecord callbacks;        synchronized (mLock) {            final long now = System.nanoTime();            // 根据指定的类型CallbackkQueue中查找到达执行时间的CallbackRecord            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(                    now / TimeUtils.NANOS_PER_MS);            if (callbacks == null) {                return;            }            mCallbacksRunning = true;            if (callbackType == Choreographer.CALLBACK_COMMIT) {                final long jitterNanos = now - frameTimeNanos;                Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);                if (jitterNanos >= 2 * mFrameIntervalNanos) {                    final long lastFrameOffset = jitterNanos % mFrameIntervalNanos                            + mFrameIntervalNanos;                    if (DEBUG_JANK) {                        Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)                                + " ms which is more than twice the frame interval of "                                + (mFrameIntervalNanos * 0.000001f) + " ms!  "                                + "Setting frame time to " + (lastFrameOffset * 0.000001f)                                + " ms in the past.");                        mDebugPrintNextFrameTimeDelta = true;                    }                    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) {                // 回调CallbackRecord的run                // 其内部回调Callback的run                c.run(frameTimeNanos);            }        } finally {            synchronized (mLock) {                mCallbacksRunning = false;                do {                    final CallbackRecord next = callbacks.next;                    recycleCallbackLocked(callbacks);                    callbacks = next;                } while (callbacks != null);            }            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }}

注意遍历 CallbackRecord 链表调用其 run 方法:

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

注意 token == FRAME_CALLBACK_TOKEN 表示通过 postFrameCallback 添加的任务。这里就是按照 Callback 类型回调其 run 方法。

回到 ViewRootImpl 发起的绘制任务,此时 View 的绘制流程便开始了。

final class TraversalRunnable implements Runnable{    @Override    public void run(){        // View 的绘制任务开始        doTraversal();    }}

至此 Choreographer 的工作流程就已经分析清楚了,Choreographer 支持四种类型任务:输入、动画、绘制和提交,并配合系统的 VSYNC 进行刷新、绘制等流程。确实做到了统一协调管理。

下面,再通过一张图来加深对 Choreographer 的工作流程的理解。


正如文章开头介绍 Choreographer 可以配合系统的 VSYNC 信号完成 UI 的绘制任务。那我们便可以通过它来监控应用的帧率,虽然 Choreographer 内部也实现了对掉帧的监控,但是默认只能监控超过 30 帧及以上。

不过通过今天的分析,你是否也可以实现一个任意掉帧数的监控呢?并且可以将其用于线上统计,更好的帮助我们优化应用的渲染性能。


关于 UI 渲染所涉及的内容非常多,文章最后也会附上一些扩展资料,便于更好的学习理解。

文中如有不妥或有更好的分析结果,欢迎您的分享留言或指正。

文章如果对你有帮助,请留个赞吧。


扩展阅读
  • 关于 UI 渲染,你需要了解什么?
  • Android 之如何优化 UI 渲染(上)
  • Android 之理解 VSYNC
  • Android 之 LayoutInflater 全面解析
  • Android 之你真的了解 View.post() 原理吗?
  • Android 之 Choreographer 详细分析

其他系列专题

  • Android 存储优化系列专题
  • Android 之权限管理只防君子不防
  • Android 之不要滥用 SharedPreferences(上)
  • Android 存储选项之 SQLite 优化那些事儿

更多相关文章

  1. 【Android】实现登录、注册、数据库操作(极复杂)
  2. android中的ANR
  3. 【Android】点击WebView中的按钮,关闭当前activity
  4. android 自定义view 前的基础知识
  5. Android图灵机器人的实现(一)
  6. (转载)Android显示GIF
  7. 详解android 接口回调
  8. Android中TouchEvent触摸事件机制
  9. Android画图之抗锯齿(转)

随机推荐

  1. Android(安卓)布局 LinearLayout与Relati
  2. :Android(安卓)Market的 Loading效果
  3. android中的命名空间
  4. 初探Android
  5. 安卓巴士Android开发神贴整理
  6. Android概述
  7. android architect
  8. Android(安卓)GridView九宫图
  9. Android高手进阶教程(三)之----Android(
  10. Android版本与Linux内核版本的关系