<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
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的繪製,所以取名編舞者。
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!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45