Android 屏幕刷新机制 Choreographer 原理分析

源码基于 sdk-30/11.0/R。

前言

  1. Android 平台提供两种信号,一种是硬件信号,另一种是软件信号,由 SurfaceFlinger 进程的一个线程定时发出,硬件信号由硬件发出;
  2. App 进程若要通过 gpu 实现图像绘制,需要在接收到 Vsync 信号的条件下进行。因此,App 进程访问 SurfaceFlinger 进程获取这个信号,再进行 gpu 绘制;
  3. Android4.1 之后增加了 Choreographer 机制,用于同 Vsync 机制配合,统一动画、输入和绘制时机;
  4. Choreographer 就是负责获取 Vsync 同步信号并控制 App 线程(主线程)完成图像绘制的类;

Choreographer 类介绍

实例初始化

/**
Coordinates the timing of animations, input and drawing.
The choreographer receives timing pulses (such as vertical synchronization) from the display subsystem then schedules work to occur as part of rendering the next display frame.
*/
public final class 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;
        }
    };

    public static Choreographer getInstance() {
        return sThreadInstance.get();
    }
}
  • 每个线程中保存一个 Choreographer 实例对象;
  • 线程初始化 Choreographer 对象时需要提供 Looper 对象,并把 Looper 绑定到 Choreographer;
  • App 中所有的 Activity 共享同一个 Choregrapher 对象,他控制者整个App中大部分视图的绘制节奏;

构造方法

public final class Choreographer {
    public static final int CALLBACK_COMMIT = 4;
    private static final int CALLBACK_LAST = CALLBACK_COMMIT;

    private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
        mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper, vsyncSource) : null;
        mLastFrameTimeNanos = Long.MIN_VALUE;
        // 计算一帧的时间,Android手机屏幕是60Hz的刷新频率,就是16ms
        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
        //使用数组保存五个链表,每个链表存相同类型的任务:输入、动画、遍历绘制等任务(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));
    }
}
  • 根据当前线程的 looper 创建 Handler 对象;
  • 变量 USE_VSYNC 用于表示系统是否是用了 Vsync 同步机制,该值是通过读取系统属性debug.choreographer.vsync来获取的。4.1 以上默认是 true;
  • 创建一个 FrameDisplayEventReceiver 对象用于请求并接收 Vsync 事件;
  • 也就是数组中有五个链表,每个链表存相同类型的任务:输入、动画、遍历绘制等任务(CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL)
  • 创建了一个大小为 5 的 CallbackQueue 队列数组,用于保存不同类型的 Callback;

Callback 类型

// Choreographer.java
 //输入事件,首先执行
public static final int CALLBACK_INPUT = 0;
//动画,第二执行
public static final int CALLBACK_ANIMATION = 1;
//插入更新的动画,第三执行
public static final int CALLBACK_INSETS_ANIMATION = 2;
//绘制 View,第四执行
public static final int CALLBACK_TRAVERSAL = 3;
//提交,最后执行。遍历完成的提交操作,用来修正动画启动时间
public static final int CALLBACK_COMMIT = 4;
  • 五种类型任务对应存入对应的 CallbackQueue 中;
  • 每当收到 VSYNC 信号时,Choreographer 将首先处理 INPUT 类型的任务,然后是 ANIMATION 类型,最后才是 TRAVERSAL 类型;

FrameHandler 处理的消息

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(System.nanoTime(), 0);
               break;
           case MSG_DO_SCHEDULE_VSYNC:
               doScheduleVsync();
               break;
           case MSG_DO_SCHEDULE_CALLBACK:
               doScheduleCallback(msg.arg1);
               break;
       }
   }
}

FrameHandler 处理了如下三种消息:

  • MSG_DO_FRAME 处理注册在 Choreographer 的 Runnable;
  • MSG_DO_SCHEDULE_VSYNC 直接请求下一帧的 VSync 信号;
  • MSG_DO_SCHEDULE_CALLBACK 处理延时消息;

Choreographer 处理任务流程

choreographer-callbacks
public abstract class DisplayEventReceiver {
    public void scheduleVsync() {
        nativeScheduleVsync(mReceiverPtr);
    }

    // Called from native code.
    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
        onVsync(timestampNanos, physicalDisplayId, frame);
    }

    // Called when a vertical sync pulse is received.
    public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
    }
}

private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
    @Override
    public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
        // timestampNanos其实是本次vsync产生的时间,从服务端发过来
        // 该消息的callback为当前对象
        Message msg = Message.obtain(mHandler, this);
        // 前面添加了同步屏障,同步消息不会被执行
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }

    @Override
    public void run() {
        doFrame(mTimestampNanos, mFrame);
    }

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

        if (frameTimeNanos < mLastFrameTimeNanos) {
            // 这种情况一般是生成vsync的机制出现了问题,那就再申请一次
            scheduleVsyncLocked();
            return;
        }

        // intendedFrameTimeNanos是本来要绘制的时间戳,frameTimeNanos是真正的,可以在渲染工具中标识延迟VSYNC多少
        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
        // 移除mFrameScheduled判断,说明处理开始了
        mFrameScheduled = false;
        
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    }

    void doCallbacks(int callbackType, long frameTimeNanos) {
        // 获取当前类型 Callback 链表的首部节点
        CallbackRecord callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                        now / TimeUtils.NANOS_PER_MS);
        try {
            // 遍历链表,执行任务。比如执行 ViewRootImpl#TraversalRunnable 任务。
            for (CallbackRecord c = callbacks; c != null; c = c.next) {
                c.run(frameTimeNanos);
            }
        } finally {
            // 遍历链表,重置 Callback
        }
    }
}

scheduleVsync() 方法通过 native 方法 nativeScheduleVsync() 向 SurfaceFlinger 服务注册,即在下一次脉冲接收后会调用 DisplayEventReceiver 的 dispatchVsync() 方法;
底层向应用层发送VSYNC信号,java 层通过 dispatchVsync() 接收,最后回调在FrameDisplayEventReceiver#onVsync();

处理绘制任务

// ViewRootImpl.java
public ViewRootImpl(Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
    //获取Choreographer实例。useSfChoreographer 默认 false。
    mChoreographer = useSfChoreographer
                ? Choreographer.getSfInstance() : Choreographer.getInstance();
}

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

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

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        // 开始测量,布局和绘制流程
        performTraversals();
    }
}

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 同步屏障的作用是可以拦截 Looper 对同步消息的获取和分发。
        // 加入同步屏障之后,Looper 只会获取和处理异步消息,如果没有异步消息那么就会进入阻塞状态;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

VSync 机制

Android 系统每隔 16ms 发出 VSYNC 信号,触发对UI进行渲染,VSync是Vertical Synchronization(垂直同步)的缩写。

VSync

VSync
  1. 16ms 内只会申请一次垂直同步信号;
  2. VSync信号由SurfaceFlinger实现并定时发送。

总结

  1. Choreographer 的职责是统一处理输入、绘制、动画和提交任务;
  2. 主线程通过 Choreographer 把不同类型的 Callback 添加到单链表中。时间到后,根据 Callback 优先级遍历各个单链表执行任务;

参考

[1] 屏幕刷新机制Choreographer原理分析
[2] Android屏幕刷新机制解析
[3] Android 屏幕刷新机制与Choreographer全面解读

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

推荐阅读更多精彩内容