Android 怎麼就不卡了呢之Choreographer


題外話android

時光如斯 人世無常 逼哥突然就被 全網封殺數組

難過 尚未去聽過現場 老是感受如今很忙 來日方長 我這種短視動物怎麼屑於把意外這種小几率東西歸入考慮範圍呢 如今和未來這兩個選項悲觀點徹底能翻譯成但願與永別緩存

封殺緣由未知 若是是由於XD 這個自媒體猖獗的時代 是個好時代 它把這個世界本有的模樣赤裸裸矗在你跟前 容許你懷疑思考進步 也是個壞時代 公信力成了僞命題 把人類最後那點遮羞布撕個粉碎 打擊相信人性是正直勇敢美麗的勇氣 若是是由於ZF 嗯 愛我中華bash

前言

針對Android UI不流暢的問題,Google提出了Project Butter對Android的顯示系統進行了重構。 此次重構的三個關鍵點異步

  • VSynch 垂直同步
  • Triple Buffer 三重緩存
  • Choreographer 編舞者

這篇文章咱們主要聊一聊Choregrapher,後續的咱們寫關於其餘。async

choreographer

界面的顯示大致會通過CPU的計算-> GPU合成柵格化->顯示設備顯示。咱們知道Android設備的刷新頻率通常都是60HZ也就是一秒60次,若是一次繪製在約16毫喵內完成時沒有問題的。可是一旦出現不協調的地方就會出問題以下圖ide

  • 第一個週期,cpu計算、GPU操做、顯示設備顯示沒有問題。
  • 第二個週期,顯示上一個週期繪製準備好的圖像;CPU計算是在週期將要結束的時候纔開始計算
  • 第三個週期,因爲進入第二個週期的時候CPU動手計算晚點了,導致進入第三個週期的時候本應該顯示的圖像沒有準備好,致使整個第三個週期內仍是顯示上一個週期的圖像,這樣看上去會卡,掉幀!google工程師們管整個叫Jank延誤軍機。

image

這事不小啊,怎麼解決呢?垂直同步 簡單的說,就是讓CPU計算別沒有計劃沒有規律而是在每一個週期開始的時候開始計算,這樣就有條不紊的有序進行了(以下圖)。這個在android4.1及之後的版本中加入了Choreographer這個類,讓咱們扒開看看他是怎麼實現的。 (Choreographer 這個類的位置android.view.Choreographer) oop

image

1、入口

1.1 postCallbackDelayedInternal

ViewRoot的 doTravle()方法中有這樣一行代碼佈局

mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
複製代碼

它的意思就是對整個View樹發起測量佈局繪製操做。關於ViewRootImpl的更多內容這裏就很少介紹了。post

如下方法

  • public void postCallback(...) ;
  • public void postCallbackDelayed(...);
  • public void postFrameCallback(FrameCallback callback);
  • public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis)

最終都會調用 postCallbackDelayedInternal();,那麼咱們就看看這個方法的功能。

private void postCallbackDelayedInternal(int callbackType,
                                             Object action, Object token, long delayMillis) {
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();//獲取當前時間
            final long dueTime = now + delayMillis;//到期時間
            //將執行動做放在mCallback數組中
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            //若是已經到期就註冊請求垂直同步信號
            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
            //若是尚未到期,使用handler在發送一個延時消息。這個延時消息會在到期的時候執行。
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }
複製代碼

1.2 FrameHandler

上一小節,若是已經到期就直接執行scheduleFrameLocked()方法,若是沒有執行就使用mHandler(FrameHandler類型)發送一個what值爲MSG_DO_SCHEDULE_CALLBACK的Message。到期後怎麼執行的呢。這要看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:
                //postCallbackDelayedInternal()方法中當未到期的時候發送過來的
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }
複製代碼

以上代碼咱們能夠看出這個,FramHandler拿到 whate屬性值爲MSG_DO_SCHEDULE_CALLBACK的時候會去執行 doScheduleCallback(msg.arg1);方法,跟進去看下

1.3 Choreography#doScheduleCallback

void doScheduleCallback(int callbackType) {
        synchronized (mLock) {
            if (!mFrameScheduled) {
                final long now = SystemClock.uptimeMillis();
                if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
                    scheduleFrameLocked(now);
                }
            }
        }
    }
複製代碼

這個方法中先是作了一些判斷,mFrameSceduled爲false 而且hasDueCallbacksLocked()這個方法的返回值爲true,看方法名就能猜出這個callback是否到期了,下面咱們再分析這個。最終若是知足條件的狀況下它會調用 scheduleFrameLocked()這個方法,咦這個方法眼熟不?對,沒錯,postCallbackDelayedInternal()方法中若是到期了的話就直接執行的那個方法。是時候看這個方法裏面搞的什麼事情了。

1.4 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);
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }
複製代碼
  • 這個方法的目的很明確就是安排,安排垂直同步並且馬上立刻儘快。安排垂直同步的條件是USE_VSYNC爲true,也就是設備支持垂直同步
  • 若是不是垂直同步就經過handler發送一個延時一個週期的消息安排垂直同步,這個Message的what值爲 MSG_DO_FRAME,參照1.2的代碼塊對what爲MSG_DO_FRAME的消息會去執行doFrame()方法。
  • 一個細節,當這個值mFrameScheduled爲true的時候就直接返回不安排請求下一幀渲染了,若是爲false,執行scheduleFrameLocked()方法繼續執行,而且將其設置爲ture;在何時設置爲false的呢?詳細細節看附錄二

安排垂直同步的具體實現是FrameDisplayEventReceiver類他是DisplayEventReceiver的用於接收垂直信號

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) {
            mTimestampNanos = timestampNanos;
            mFrame = frame;
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);//Message設置爲異步
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }
        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }
    }
複製代碼

接收到垂直同步信號後回調onVsync方法,這個方法使用handler發送帶callback(Runnable類型,自身已繼承)的message,最後run()中也是調用doFrame();(關於這個handler的這個操做詳細信息邏輯,參照下面本文附錄一 handler 分發message

這個message設置爲了異步 (msg.setAsynchronous(true);)這意味這他有優先執行的權利,他是怎麼被優先執行的呢?參照附錄三 message的異步模式

綜上,添加callback流程

image

2、執行

doFrame

void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }
        
            //當前時間
            startNanos = System.nanoTime();
            //當前時間和垂直同步時間
            final long jitterNanos = startNanos - frameTimeNanos;
            //垂直同步時間和當前時間的差值若是大於一個週期就修正一下
            if (jitterNanos >= mFrameIntervalNanos) {
            //取插值和始終週期的餘數
               final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
               //當前時間減去上一步獲得的餘數看成最新的始終信號時間
               frameTimeNanos = startNanos - lastFrameOffset;
            }
            //垂直同步時間上一次時間還小,就安排下次垂直,直接返回
            if (frameTimeNanos < mLastFrameTimeNanos) {
                scheduleVsyncLocked();
                return;
            }
            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            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);

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

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

        if (DEBUG_FRAMES) {
            final long endNanos = System.nanoTime();
            Log.d(TAG, "Frame " + frame + ": Finished, took "
                    + (endNanos - startNanos) * 0.000001f + " ms, latency "
                    + (startNanos - frameTimeNanos) * 0.000001f + " ms.");
        }
    }
複製代碼
  1. 第一步修正判斷
  • 當前時間 startNanos = System.nanoTime();

  • 求當前時間和垂直同步時間的差值 :jitterNanos = startNanos - frameTimeNanos;

  • 垂直同步時間和當前時間的差值若是大於一個週期(jitterNanos >= mFrameIntervalNanos)就修正一下

    • 取插值和始終週期的餘數:lastFrameOffset = jitterNanos % mFrameIntervalNanos;
    • 當前時間減去上一步獲得的餘數看成最新的始終信號時間:frameTimeNanos = startNanos - lastFrameOffset;
  • 垂直同步時間上一次時間還小,就安排下次渲染: frameTimeNanos < mLastFrameTimeNanos,直接返回

  1. 第二步‍執行callback callback的執行順序是:
  • CALLBACK_INPUT輸入時間優先級最高
  • CALLBACK_ANIMATION 動畫的次之
  • CALLBACK_TRAVERSAL UI繪製佈局的再次之
  • CALLBACK_COMMIT動畫修正相關最後。

2.2 doCallbacks();

  • CallbackQueue[] mCallbackQueues在取特定類型(輸入,動畫,佈局,Commit)的單向鏈表
  • 而後取出已到期的Callback/Runable執行

取出須要被執行的Actions

Action包裝在CallbackRecord中,是一個單向列表,按照時間的大小順序排列的。 取出待執行的Actions是經過CallBackQueue的extractDueCallbacksLocked()方法,能夠把CallBackQueue看作是CallBack的管理類,其中還包括添加Action addCallbackLocked(),移除Action removeCallbacksLocked(),是否有帶起的Anction hasDueCallbacksLocked()方法。

private final class CallbackQueue {
        //鏈表頭
        private CallbackRecord mHead;
        //是否存在已經到期的Action
        public boolean hasDueCallbacksLocked(long now) {
            return mHead != null && mHead.dueTime <= now;
        }
        //獲取已經到期的Action
        public CallbackRecord extractDueCallbacksLocked(long now) {
            ...
            return callbacks;
        }
        
        //添加Action
        public void addCallbackLocked(long dueTime, Object action, Object token) {
         ...
        }
        //移除Action
        public void removeCallbacksLocked(Object action, Object token) {
          ...
        }
    }
複製代碼

執行Action

for (CallbackRecord c = callbacks; c != null; c = c.next) {
                c.run(frameTimeNanos);
    }
    
複製代碼

從callback中遍歷出CallBcakRecord,挨個執行。

3、小結

  • Choreographer對外提供了postCallback等方法,最終他們內部都是經過調用postCallbackDelayedInternal()實現這個方法主要會作兩件事情
    • 存儲Action
    • 請求垂直同步,垂直同步
  • 垂直同步回調立馬執行Action(CallBack/Runnable)。
  • Action一個動做內容的類型多是
    • CALLBACK_INPUT輸入時間優先級最高
    • CALLBACK_ANIMATION 動畫的次之
    • CALLBACK_TRAVERSAL UI繪製佈局的再次之
    • CALLBACK_COMMIT動畫修正相關最後。
  • 複習Hanlder機制,我認爲他是Android系統跑起來的大引擎終點關注下,handler對message的分發執行,以及「異步模式」。

附2、關於handler執行Message

下面是handler分發邏輯,Looper在MessageQueue獲得要執行的message以後就會交給message的target(Handler類型)屬性處理msg.target.dispatchMessage(msg);;

public void dispatchMessage(Message msg) {
    //當msg的callback不爲空的時候直接執行msg的callback它是一個Runnable對象
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
    //而後再交給mCallBack,它是handler的一個屬性, 
    //建立Handler的時候能夠選擇傳入一個CallBack對象
    //當callBack中handleMessage返回true的時候表示:True if no further handling is desired(不須要進一步處理)
  
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
    //當mCallback處理返回爲false的時候纔去執行Handler自身的handleMessage()方法
            handleMessage(msg);
        }
    }
複製代碼

關鍵邏輯在已註釋,小結一下 handler的執行分發Message邏輯

  1. 若是message的callback(runnable)屬性不爲空,調用這個runable的run()方法執行
private static void handleCallback(Message message) {
        message.callback.run();
    }

複製代碼

當咱們使用handler.post(Runnable r)方法時候就是將r設置給message的callback

  1. 上述條件不知足的狀況下,若是handler自身的mCallback不爲空的時候就會,將message交給mCallback處理,handlerMessage()結束。這個屬性是在handler建立的時候傳入的。mCallback是CallBack類型,他是handler的一個內部接口。
public interface Callback {
         boolean handleMessage(Message msg);
    }
複製代碼

3.當messaga 的callBak爲空,且handler的mCallBack爲空的時候就交給本身的handlerMessage()方法執行了。咱們在自定義handler的時候能夠重寫這個方法對message進行相應的操做。

附二 、mFrameScheduled屬性做用

  • 執行callcack的時候會判斷mFrameScheduled屬性若是爲false表示沒有安排渲染下一幀就直接返回,不執行。
void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }
            ...
            ...
            mFrameScheduled = false;
           ...
        }
複製代碼
  • 在scheduleFrameLocked()方法中,將mFrameScheduled值設置爲ture表示安排了請求渲染下一幀。若是這時mFrameScheduled爲true表示已經安排了下一幀那麼就返回,不添亂!

附3、Handler機制的異步模式

做用

「異步模式」的做用就是優先,asynchronous 的message在異步模式下有優先執行的權。

用法

MessageQueue使用postSyncBarrier()方法添加屏障,removeSyncBarrier()方法移除屏障這個兩個方法是成對使用的。

實現原理

  • messageQueued的postSyncBarrier方法向messagequeue的頭部添加一個target屬性爲null的message
  • messageQueue的next()當碰到target爲null的message的時候就只會在message鏈表中取出去「異步message」,而忽略普通的message,交給Looper作進一步分發處理。
Message next() {
   ...
   for (;;) {
       if (nextPollTimeoutMillis != 0) {
           Binder.flushPendingCommands();
       }
       nativePollOnce(ptr, nextPollTimeoutMillis);
       synchronized (this) {
           // Try to retrieve the next message.  Return if found.
           final long now = SystemClock.uptimeMillis();
           Message prevMsg = null;
           Message msg = mMessages;
           if (msg != null && msg.target == null) {
               // Stalled by a barrier.  Find the next asynchronous messagin the queue.
               do {
                   prevMsg = msg;
                   msg = msg.next;
               } while (msg != null && !msg.isAsynchronous());
           }                       ...
           return msg;
         }
  ...
複製代碼

完,水平有限,各位不吝批評指正。

相關文章
相關標籤/搜索