首頁 > 軟體

Android Choreographer原始碼詳細分析

2022-08-26 18:05:47

一、首先介紹一些基礎知識

1.重新整理率(Refresh Rate):

重新整理率代表螢幕在一秒內重新整理螢幕的次數,用赫茲來表示。赫茲是頻率的單位,一秒震動的次數。這個重新整理率取決於硬體固定的引數。這個值一般是60Hz。即每16.66ms重新整理一次螢幕。

2.幀速率(Frame Rate):

幀速率代表了GPU在一秒內繪製操作的幀數。比如30FPS、60FPS。Frame Per Second。

3.如果兩個裝置獨立執行,如果重新整理率和幀速率不同步,會引發以下兩種問題。

如果幀速率高於重新整理率,製作的頻率大於展示的頻率,會導致螢幕影象的展示的跳躍,跳幀的現象。

如果幀速率小於重新整理率,製作的頻率小於展示的頻率,會導致螢幕影象的展示的中斷,掉幀的現象。

4.android為了解決上面的問題,在4.1版本中引入了Projectbuffer.

ProjectBuffer對Android Display系統進行了重構,引入了三個核心元素,即Vsync,TripleBuffer和Choreographer。

其中Vsync是Vertical Synchronization 垂直同步是縮寫。是一種在PC上已經很早就廣泛使用的技術。

引入是Vsync來進行控制CPUGPU的繪製和螢幕重新整理同步進行。

而編舞者choreography的引入,主要是配合Vsync,給上層App的渲染提供一個穩定的時機。Vsync到來的時候,Choreographer可以根據Vsync訊號,統一管理應用的輸入、動畫、繪製等任務的執行情況。Choreographer就像一個指揮家一樣,來把控著UI的繪製,所以取名編舞者。

二、android原始碼中Choreographer是如何執行

1.首先在ViewRootImpl建構函式中建立了Choreographer物件

 public ViewRootImpl(Context context, Display display) {
     mChoreographer = Choreographer.getInstance();
 }
  public static Choreographer getInstance() {
         return sThreadInstance.get();
  }

當呼叫get時,如果為null,會呼叫initialValue()方法。並把Choreographer範例和ThreadLocal繫結。

private static final ThreadLocal<Choreographer> sThreadInstance =
        new ThreadLocal<Choreographer>() {
    @Override
    protected Choreographer initialValue() {
        //因為後面會用到handler通訊,所以必須有一個Looper迴圈
        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);
        //如果是主執行緒,則把choreographer賦值給mMainInstance
        if (looper == Looper.getMainLooper()) {
            mMainInstance = choreographer;
        }
        return choreographer;
    }
};

2.看Choreographer建構函式

mLastFrameTimeNanos:記錄上一幀繪製的時間。

mFrameIntervalNanos:螢幕繪製一幀的時間間隔,這個是納秒值。如果螢幕重新整理率是60Hz,那麼重新整理一幀的時間間隔就是16.66.......毫秒。

private Choreographer(Looper looper, int vsyncSource) {
    // 傳一個Looper進來,
    mLooper = looper;
    //用來處理訊息的。
    mHandler = new FrameHandler(looper);
    //USE_VSYNC 是否使用Vsync
    //boolean USE_VSYNC = SystemProperties.getBoolean("debug.choreographer.vsync", true);
     mDisplayEventReceiver = USE_VSYNC
                    ? new FrameDisplayEventReceiver(looper, vsyncSource)
                    : null;
    //上一幀繪製的時間
    mLastFrameTimeNanos = Long.MIN_VALUE;
    //1秒是1000毫秒,1毫秒是1000微秒,1微秒是1000納秒
    //1秒就是1*1000*1000*1000=10的九次方納秒
    //繪製一幀的時間間隔----納秒。如果是60Hz,那麼重新整理一幀展示的時間就是16.66毫秒。
    mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
    //初始化回撥佇列,後面會從這個回撥佇列中取出Runnable執行run方法。
     mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
    for (int i = 0; i <= CALLBACK_LAST; i++) {
        mCallbackQueues[i] = new CallbackQueue();
    }
}

獲取螢幕的重新整理率:

//螢幕的重新整理率,一秒鐘可以重新整理螢幕多少次,通常是60Hz
private static float getRefreshRate() {
    DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
            Display.DEFAULT_DISPLAY);
    return di.getMode().getRefreshRate();
}

3.初始化工作完成,那麼Choreographer是怎麼跑起來的,入口函數在哪?

對於UI繪製來說是入口在RootViewImpl的scheduleTraversals()方法中。

void scheduleTraversals() {
 if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //傳送一個屏障訊息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //注意第一個引數CALLBACK_TRAVERSAL,回撥函數的型別。
            //mTraversalRunnable 回撥函數要執行的runnable。
            //第三個引數token,傳了一個null
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
 }

//第一個引數callbackType 有五種型別,這幾個回撥是有順序的。
1.CALLBACK_INPUT 輸入回撥,首先執行
2.CALLBACK_ANIMATION 動畫回撥,這個在將動畫原理的時候,會看到
3.CALLBACK_INSETS_ANIMATION inset和update相關的動畫,執行在上面兩個回撥之後,
4.CALLBACK_TRAVERSAL 遍歷回撥,用於處理佈局和繪製
5.CALLBACK_COMMIT Commit回撥,在Traversal繪製回撥之後。

接下來看postCallbackDelayedInternal方法

第二個引數就是上面的mTraversalRunnable。
第四個引數延遲的時間,這裡延遲時間是0,沒有延遲
所以這個方法走if判斷的第一個分支

private void postCallbackDelayedInternal(int callbackType,
         Object action, Object token, long delayMillis) {
     synchronized (mLock) {
         final long now = SystemClock.uptimeMillis();
         final long dueTime = now + delayMillis;
         //將runnable加入回撥佇列
         mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
         上面傳過來的delayMillis是0,所以走第一個分支。
         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);
         }
     }
 }
private void scheduleFrameLocked(long now) {
     if (!mFrameScheduled) {
                mFrameScheduled = true;
        //如果使用垂直同步
         if (USE_VSYNC) {
                //判斷是否執行在主執行緒,如果是則直接呼叫scheduleVsyncLocked()
                //如果執行在子執行緒則通過傳送handler 的方式也會呼叫到scheduleVsyncLocked()
                if (isRunningOnLooperThreadLocked()) {//Looper.myLooper() == mLooper
                    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);
         }
     }
 }

scheduleVsyncLocked()方法。

private void scheduleVsyncLocked() {
     //呼叫父類別 DisplayEventReceiver的方法
     mDisplayEventReceiver.scheduleVsync();
}

在scheduleVsync()方法中會呼叫nativeScheduleVsync,這是一個native方法,在native層執行完畢後會回撥到java層的方法dispatchVsync()

scheduleVsync:向native層去請求一個Vsync訊號。

dispatchVsync:請求到Vsync訊號後,執行Java層的UI繪製和渲染邏輯。

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 { // 呼叫native 方法
        //呼叫Native方法請求一個Vsync訊號,然後會從native層回撥java層的dispatchVsync方法
        nativeScheduleVsync(mReceiverPtr);
    }
}

timestampNanos:從Native層傳遞過來的一個時間戳,Vsync從native層發出的時間。

 // Called from native code.
//從native層回撥java層的dispatchVsync方法
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
    onVsync(timestampNanos, physicalDisplayId, frame);
}

在這又傳送了一個非同步訊息,並且 Message.obtain(mHandler, this);第二個引數是一個callBack回撥。所以沒有handler的情況下,會執行這個回撥函數。但是傳的是this,所以就會執行this的run方法。這個this就是FrameDisplayEventReceiver的範例,在Choreographer的建構函式中初始化的。

 public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
      mTimestampNanos = timestampNanos;
        mFrame = frame;
        //得到message 新增了一個回撥函數,this,則會呼叫run方法
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
 }

在FrameDisplayEventReceiver的run方法中,呼叫的doFrame方法

 @Override
 public void run() {
   mHavePendingVsync = false;
   doFrame(mTimestampNanos, mFrame);
 }
void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if (!mFrameScheduled) {
                return; // no work to do
            }
            //sync訊號發出的時間,
            long intendedFrameTimeNanos = frameTimeNanos;
            //當前的時間
            startNanos = System.nanoTime();
            //兩者相減得到的時間差,就是底層訊息通訊和回撥所消耗的時間
            final long jitterNanos = startNanos - frameTimeNanos;
            //如果這個時間差大於了一幀的時間間隔。
            if (jitterNanos >= mFrameIntervalNanos) {
                //計算跳過了多少幀
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                //注意下面這行日子,如果跳幀大於30幀,系統會列印下面這行log,在主執行緒做了太多工作,會造成UI卡頓。
                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;
                }
                //用當前時間減去多出來的時間,就是下一幀要繪製的時間
                //進行繪製時間的修正,保證每一次的繪製時間間隔都是mFrameIntervalNanos
                frameTimeNanos = startNanos - lastFrameOffset;
            }
             //如果底層傳過來的時間,小於上一幀繪製的時間,正常情況下,frameTimeNanos都是大於上一幀繪製的時間的。
            if (frameTimeNanos < mLastFrameTimeNanos) {
                //跳過本次的繪製,請求下一幀的時間
                scheduleVsyncLocked();
                return;
            }
            //以上的判斷,都是為了控制繪製的頻率。
            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    scheduleVsyncLocked();
                    return;
                }
            }
            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            //重置標誌位,可以再次進入scheduleFrameLocked
            mFrameScheduled = false;
            //將底層傳過來的時間,記錄為本次繪製的時間,也就是下一幀傳過來時,上一幀繪製的時間。
            mLastFrameTimeNanos = frameTimeNanos;
        }
        try {
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
            mFrameInfo.markInputHandlingStart();
            //根據Choreographer的CallBack型別,進行callBack的回撥。
            //輸入
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
            mFrameInfo.markAnimationsStart();
            //動畫
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
            mFrameInfo.markPerformTraversalsStart();
            //介面繪製
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
            //commit
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

這個是很重要的一個方法。

通過這個方法中的邏輯能夠看出:Choreographer控制App層UI繪製的節奏和頻率。

然後會按順序執行一些列的doCallBacks函數。

首先會根據callbackType,從連結串列中取出CallBackRecord。然後再遍歷CallBackRecord,呼叫他的run方法。

void doCallbacks(int callbackType, long frameTimeNanos) {
   CallbackRecord callbacks;
     synchronized (mLock) {
          final long now = System.nanoTime();
             //根據callbacktype,從連結串列中拿到 CallbackRecord
              callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                      now / TimeUtils.NANOS_PER_MS);
              if (callbacks == null) {
                  return;
              }
              mCallbacksRunning = true;
             for (CallbackRecord c = callbacks; c != null; c = c.next) {
                   //執行CallbackRecord的run方法
                    c.run(frameTimeNanos);
             }
     }
 }

根據token來進行區分是FrameCallback型別還是Runnable。

主要這裡的token傳進來的是null,所以會執行else分支。

這個action就是mTraversalRunnable,呼叫mTraversalRunnable的run方法。

mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

public void run(long frameTimeNanos) {
     if (token == FRAME_CALLBACK_TOKEN) {
         ((FrameCallback)action).doFrame(frameTimeNanos);
     } else {
         ((Runnable)action).run();
     }
}
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

在它的run方法中執行了doTraversal()。

void doTraversal() {
     if (mTraversalScheduled) {
         mTraversalScheduled = false;
         //刪除屏障訊息
         mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
         //呼叫測量、佈局和繪製方法
         performTraversals();
     }
 }

performTraversals()方法中就會呼叫
performMeasure、performLayout、performDraw,對View進行測量、佈局、和繪製。

到此這篇關於Android Choreographer原始碼詳細分析的文章就介紹到這了,更多相關Android Choreographer內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com