您好,登錄后才能下訂單哦!
本文主要解決以下幾個問題:
好了,帶著以上問題,我們進(jìn)入源碼來找尋答案。
一、屏幕繪制流程
屏幕繪制機(jī)制的基本原理可以概括如下:
整個屏幕繪制的基本流程是:
如果放到Android中來,那么就是:
在Android中,一塊Surface對應(yīng)一塊內(nèi)存,當(dāng)內(nèi)存申請成功后,App端才有繪圖的地方。由于Android的view繪制不是今天的重點(diǎn),所以這里點(diǎn)到為止~
二、屏幕刷新分析
屏幕刷新的時(shí)機(jī)是當(dāng)Vsync信號到來的時(shí)候,具體如圖:
在Android端,是誰在控制 Vsync
的產(chǎn)生?又是誰來通知我們應(yīng)用進(jìn)行刷新的呢? 在Android中, Vysnc
信號的產(chǎn)生是由底層 HWComposer
負(fù)責(zé)的,而通知應(yīng)用進(jìn)行刷新,是Java層的 Choreographer
,Android整個屏幕刷新的核心就在于這個 Choreographer
。
下面我們結(jié)合代碼一起來看一下。
每次當(dāng)我們要進(jìn)行ui重繪的時(shí)候,都會調(diào)用 requestLayout()
,所以,我們從這個方法入手:
2.1 requestLayout()
----》類名:ViewRootImpl @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; //重點(diǎn) scheduleTraversals(); } }
2.2 scheduleTraversals()
----》類名:ViewRootImpl void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ...... } }
可以看到,在這里并沒有立即進(jìn)行重繪,而是做了兩件事情:
接下來,我們簡單說一下這個 SyncBarrier
(同步屏障)。
異步屏障的作用在于:
為什么要設(shè)計(jì)這個 SyncBarrier
呢?主要原因在于,在Android中,有些消息是十分緊急的,需要馬上執(zhí)行,如果說消息隊(duì)列里面普通消息太多的話,那等到執(zhí)行它的時(shí)候可能早就過了時(shí)機(jī)了。
到這里,可能有人會跟我一樣,覺得為什么不干脆在Message里搞個優(yōu)先級,按照優(yōu)先級來進(jìn)行排序呢?弄個 PriorityQueue
不就完了嗎?
我自己的理解是,在Android中,消息隊(duì)列的設(shè)計(jì)是一個 單鏈表
,整個鏈表的排序是根據(jù)時(shí)間進(jìn)行排序的,如果此時(shí)再加入一個優(yōu)先級的排序規(guī)則,一方面會復(fù)雜會排序規(guī)則,另一方面,也會使得消息不可控。因?yàn)閮?yōu)先級是可以用戶自己在外面填的,那樣不就亂套了嗎?如果用戶每次總填最高的優(yōu)先級,這樣就會導(dǎo)致系統(tǒng)消息很久才會消費(fèi),整個系統(tǒng)運(yùn)作就會出問題,最后影響用戶體驗(yàn),所以,我自己覺得Android的同步屏障這個設(shè)計(jì)還是挺巧妙的~
好了,總結(jié)一下,執(zhí)行 scheduleTraversals()
后,會插入一個屏障,保證異步消息的優(yōu)先執(zhí)行。
插入一個小小的思考題: 如果說我們在一個方法里連續(xù)調(diào)用了 requestLayout()
多次,那么請問:系統(tǒng)會插入多條屏障或者 post
多個 Callback
嗎? 答案是不會,為什么呢?看到 mTraversalScheduled
這個變量了嗎?它就是答案~
2.3 Choreographer.postCallback()
先來簡單說一下 Choreographer
, Choreographer
中文翻譯叫 編舞者
,它的主要作用是進(jìn)行系統(tǒng)協(xié)調(diào)的。(大家可以上網(wǎng)google下實(shí)際工作中的編舞者,這個類名真的起的很貼切了~)
Choreographer
這個類是應(yīng)用怎么初始化的呢?是通過 getInstance()
方法:
public static Choreographer getInstance() { return sThreadInstance.get(); } // Thread local storage for the 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; } };
這里貼出來是為了提醒大家, Choreographer
不是單例,而是每個線程都有單獨(dú)的一份。
好了,回到我們的代碼:
----》類名:Choreographer //1 public void postCallback(int callbackType, Runnable action, Object token) { postCallbackDelayed(callbackType, action, token, 0); } //2 public void postCallbackDelayed(int callbackType, Runnable action, Object token, long delayMillis) { .... postCallbackDelayedInternal(callbackType, action, token, delayMillis); } //3 private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { ... mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime <= now) { scheduleFrameLocked(now); } else { ... } }
Choreographer
post的callback會放入 CallbackQueue
里面,這個 CallbackQueue
是一個單鏈表。
首先會根據(jù)callbackType得到一條 CallbackQueue
單鏈表,之后會根據(jù)時(shí)間順序,將這個callback插入到單鏈表中;
2.4 scheduleFrameLocked()
----》類名:Choreographer private void scheduleFrameLocked(long now) { ... // 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 { ... } } }
scheduleFrameLocked
的作用是:
Cherographer
的工作線程的話,那么就直接執(zhí)行 scheduleVysnLocked
跟蹤源代碼,我們發(fā)現(xiàn),其實(shí) MSG_DO_SCHEDULE_VSYNC
這條消息,最終執(zhí)行的也是 scheduleFrameLocked
這個方法,所以我們直接跟蹤 scheduleVsyncLocked()
這個方法。
2.5 scheduleVsyncLocked()
----》類名:Choreographer private void scheduleVsyncLocked() { mDisplayEventReceiver.scheduleVsync(); } ----》類名:DisplayEventReceiver 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 { //mReceiverPtr是Native層一個類的指針地址 //這里這個類指的是底層NativeDisplayEventReceiver這個類 //nativeScheduleVsync底層會調(diào)用到requestNextVsync()去請求下一個Vsync, //具體不跟蹤了,native層代碼更長,還涉及到各種描述符監(jiān)聽以及跨進(jìn)程數(shù)據(jù)傳輸 nativeScheduleVsync(mReceiverPtr); } }
這里我們可以看到一個新的類: DisplayEventReceiver
,這個類的作用是注冊Vsync信號的監(jiān)聽,當(dāng)下個Vsync信號到來的時(shí)候就會通知到這個 DisplayEventReceiver
了。
在哪里通知呢?源碼里注釋寫的非常清楚了:
----》類名:DisplayEventReceiver // Called from native code. <---注釋還是很良心的 private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) { onVsync(timestampNanos, builtInDisplayId, frame); }
當(dāng)下一個Vysnc信號到來的時(shí)候,會最終調(diào)用 onVsync
方法:
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { }
點(diǎn)進(jìn)去一看,是個空實(shí)現(xiàn),回到類定義,原來是個抽象類,它的實(shí)現(xiàn)類是: FrameDisplayEventReceiver
,定義在 Cherographer
里面:
----》類名:Choreographer private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { .... }
2.6 FrameDisplayEventReceiver.onVysnc()
----》類名:Choreographer private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { @Override public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { .... 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() { .... doFrame(mTimestampNanos, mFrame); } }
onVsync
方法往 Cherographer
所在線程的消息隊(duì)列中發(fā)送的一個消息,這個消息是就是它自己(它實(shí)現(xiàn)了Runnable),所以最終會調(diào)用到 doFrame()
方法。
2.7 doFrame(mTimestampNanos, mFrame)
doFrame()的處理分為兩個階段:
void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { //1、階段一 long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime(); final long jitterNanos = startNanos - frameTimeNanos; if (jitterNanos >= mFrameIntervalNanos) { final long skippedFrames = jitterNanos / mFrameIntervalNanos; if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); } ... } ... }
frameTimeNanos
是當(dāng)前的時(shí)間戳,將當(dāng)前的時(shí)間和開始時(shí)間相減,得到這一幀處理花費(fèi)了多長,如果大于 mFrameIntervalNano
,說明處理耗時(shí)了,之后就打印出我們?nèi)粘R姷降?The application may be doing too much work on its main thread
。
階段二:
void doFrame(long frameTimeNanos, int frame) { ... try { //階段2 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); } ... }
doFrame()
的第二個階段做的是處理各種callback,從CallbackQueue里面取出到執(zhí)行時(shí)間的callback進(jìn)行處理,那這個callback是怎么樣呢?
這里要回憶一下之前的 postCallback()
操作:
這個 Callback
其實(shí)就一個 mTraversalRunnable
,它是一個 Runnable
,最終會調(diào)用到 run()
方法,實(shí)現(xiàn)界面的真正刷新:
----》類名:ViewRootImpl final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { ... performTraversals(); ... } } private void performTraversals() { ... //開始真正的界面繪制 performDraw(); ... }
三、總結(jié)
經(jīng)過漫長的代碼跟蹤,整個界面刷新流程算是跟蹤完了,下面我們來總結(jié)一下:
四、問題解答
我們都知道Android的刷新頻率是60幀/秒,這是不是意味著每隔16ms就會調(diào)用一次onDraw方法?
這里60幀/秒是屏幕刷新頻率,但是是否會調(diào)用onDraw()方法要看應(yīng)用是否調(diào)用requestLayout()進(jìn)行注冊監(jiān)聽。
如果界面不需要重繪,那么還16ms到后還會刷新屏幕嗎?
如果不需要重繪,那么應(yīng)用就不會受到Vsync信號,但是還是會進(jìn)行刷新,只不過繪制的數(shù)據(jù)不變而已;
我們調(diào)用invalidate()之后會馬上進(jìn)行屏幕刷新嗎?
不會,到等到下一個Vsync信號到來
我們說丟幀是因?yàn)橹骶€程做了耗時(shí)操作,為什么主線程做了耗時(shí)操作就會引起丟幀
原因是,如果在主線程做了耗時(shí)操作,就會影響下一幀的繪制,導(dǎo)致界面無法在這個Vsync時(shí)間進(jìn)行刷新,導(dǎo)致丟幀了。
如果在屏幕快要刷新的時(shí)候才去OnDraw()繪制,會丟幀嗎?
這個沒有太大關(guān)系,因?yàn)閂sync信號是周期的,我們什么時(shí)候發(fā)起onDraw()不會影響界面刷新;
五、參考文檔
gityuan大神的 Cherographer原理
慕課視頻
到此這篇關(guān)于說說Android的UI刷新機(jī)制的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Android UI刷新機(jī)制內(nèi)容請搜索億速云以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持億速云!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。