Android 图形系统(6)---- Choreographer 解析

Choreographer 概述

Android 4.1 之后增加了Choreographer 机制,用于同Vsync配合,同步处理输入,动画,绘制三个UI操作,实际上UI显示每一帧的时候要完成的操作只有这几样,下面是官网的说明:

  • Choreographer接收显示系统的垂直同步信号(Vsync),在下一个Frame 渲染的时候执行这些操作
  • Choreographer 中文的含义是编舞者,可以理解为知道UI操作一起顺序协调工作

通过Choreographer#postFrameCallback设置callback,再下一个Frame 被渲染的实际到来时,会执行这个callback,是
Choreographer 中的Looper 对象执行的
Callback 有四种类型 input,animation,traversal,commit 这四种操作都是 Choreographer 通过Vsync 触发的

Choreographer 解析

Choreographer 类图:


choreographer_classDiagram.jpg
View 更新流程
Choreographer 实例化

Choreographer 是单例模式,ViewRootImpl 通过 Choreographer.getInstance() 函数获取 Choreographer的实例
实现如下:

    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

通过上面的ThreadLocal,保证每一个线程对应一个Choreographer,存储的方式是以ThreadLocal作为Key,Choreographer作为value存储到ThreadLocalMap中

Choreographer 申请Vsync 流程

Choreographer 的申请Vsync流程图:


choreographer_mainflow01.jpg
choreographer_mainflow02.jpg

在ViewRootImpl 中调用了 postCallback 方法后,可以看到通过 addCallBackLocked方法,添加了一条CallbackRecord 数据,其中的action 就是对应之前的ViewRootIml中的mTraversalRunnable
然后判断时间是否在当前时间之后,也就是有没有延迟,如果有延迟就发送延迟消息MSG_DO_SCHEDULE_CALLBACK 到 Handler 所在的线程,Handler 的处理函数在 FrameHandler 类中,会执行
doScheduleCallback函数中的scheduleFrameLocked 方法;

scheduleFrameLocked 用于申请Vsync信号,在该函数中首先判断是否开启了Vsync;

  1. 如果没有开启Vsync,直接调用 doFrame方法;
  2. 如果开启了Vsync,判断在不在主线程中,如果在主线程中就运行scheduleVsyncLocked 函数;如果不在就切换线程,也会调用到scheduleVsyncLocked 方法,这个方法就是实际上用于申请 Vsync 的方法了

另外需要注意的是,使用choreographer中的handler 发送消息的时候,都调用了msg.setAsynchronous(true);方法;这个方法就是设置消息为异步消息,因为我们一开始的时候设置了同步屏障,所以异步消息就会先执行,这里设置为异步也就是为了让消息第一时间执行而不是受到其他Handler的影响

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  • scheduleVsyncLocked 执行流程
    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }

结合上面的类图,原理就是通过FrameDisplayEventReceiver 类型的mDisplayEventReceiver,请求native 层面的垂直同步信号Vsync

mDisplayEventReceiver 是 choreographer类中的成员,FrameDisplayEventReceiver 继承自 DisplayEventReceiver;设计就是为了处理Vsync 信号的申请和接收

使用 DisplayEventReceiver 的函数nativeScheduleVsync申请Vsync 信号,然后接收到Vsync信号的时候就会回调onVsync方法了

        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
            // Ignore vsync from secondary display.
            // This can be problematic because the call to scheduleVsync() is a one-shot.
            // We need to ensure that we will still receive the vsync from the primary
            // display which is the one we really care about.  Ideally we should schedule
            // vsync for a particular display.
            // At this time Surface Flinger won't send us vsyncs for secondary displays
            // but that could change in the future so let's log a message to help us remember
            // that we need to fix this.
            if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
                Log.d(TAG, "Received vsync from secondary display, but we don't support "
                        + "this case yet.  Choreographer needs a way to explicitly request "
                        + "vsync for a specific display to ensure it doesn't lose track "
                        + "of its scheduled vsync.");
                scheduleVsync();
                return;
            }

            // Post the vsync event to the Handler.
            // The idea is to prevent incoming vsync events from completely starving
            // the message queue.  If there are no messages in the queue with timestamps
            // earlier than the frame time, then the vsync event will be processed immediately.
            // Otherwise, messages that predate the vsync event will be handled first.
            long now = System.nanoTime();
            if (timestampNanos > now) {
                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
                        + " ms in the future!  Check that graphics HAL is generating vsync "
                        + "timestamps using the correct timebase.");
                timestampNanos = now;
            }

            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;
            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 函数中,同样通过Handler 发送了一条消息,执行了本身的 Runnable 回调方法,也就是doFrame

Choreographer doFrame流程
  1. 设置当前帧的开始绘制时间,上面说过开始绘制要在vsync信号来的时候开始,保证两者时间对应;所以如果时间没对上,就是发送了跳帧,那么就要修正这个时间,保证后续的时间对应上
  2. 执行所有的Callback任务
Choreographer doCallback流程
 void doFrame(long frameTimeNanos, int frame) {
......
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);

    mFrameInfo.markPerformTraversalsStart();
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
    AnimationUtils.unlockAnimationClock();
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

按照类型,从mCallbackQueues队列中取任务,并执行对应的CallbackRecord的run方法
在这个run方法中,判断了token,并且执行了action 对应的方法;
回顾一下 ViewrootImpl 中传入的方法

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

传入的方法就是ViewRootImpl 内定义的mTraversalRunnable 对象,所以最终又回到了ViewRootImpl 本身,通过Choreographer申请了Vsync信号,然后Choreographer接收了Vsync信号,在Traversal的流程中调用了本身的doTraversal函数

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();
 
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

doTraversal 调用了 performTraversals方法,也就开始了测量,布局,绘制的步骤,同时关闭了同步屏障

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容