Android的16ms和垂直同步以及三重快取
前言
手機螢幕是由許多的畫素點組成的,每個畫素點通過顯示不同的顏色最終螢幕呈現各種各樣的影象。手機系統的型別和手機硬體的不同導致UI的流暢性體驗個不一致。
螢幕展示的顏色資料
- 在GPU中有一塊緩衝區叫做 Frame Buffer ,這個幀緩衝區可以認為是儲存畫素值的二位陣列。
- 陣列中的每一個值就對應了手機螢幕的畫素點需要顯示的顏色。
- 由於這個幀緩衝區的數值是在不斷變化的,所以只要完成對螢幕的重新整理就可以顯示不同的影象了.。
- 至於重新整理工作手記的邏輯電路會定期的重新整理 Frame Buffer的 目前主流的重新整理頻率為60次/秒 折算出來就是16ms重新整理一次。
GPU的Frame Buffer中的資料
- GPU 除了幀緩衝區用以交給手機螢幕進行繪製外. 還有一個緩衝區 Back Buffer 這個用以交給應用的,讓CPU往裡面填充資料。
- GPU會定期交換 Back Buffer 和 Frame Buffer ,也就是對Back Buffer中的資料進行柵格化後將其轉到 Frame Buffer 然後交給螢幕進行顯示繪製,同時讓原先的Frame Buffer 變成 Back Buffer 讓程式處理。
Android的16ms
在Android中我們一般都會提到16ms
繪製一次,那麼到底是那裡控制這16ms的呢?
在Choreographer
類中我們有一個方法獲取螢幕重新整理速率:
public final class Choreographer {
private static float getRefreshRate() {
DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
Display.DEFAULT_DISPLAY);
return di.refreshRate;
}
}
/**
* Describes the characteristics of a particular logical display.
* @hide
*/
public final class DisplayInfo implements Parcelable {
/**
* The refresh rate of this display in frames per second.
* <p>
* The value of this field is indeterminate if the logical display is presented on
* more than one physical display.
* </p>
*/
public float refreshRate;
}
final class VirtualDisplayAdapter extends DisplayAdapter {
private final class VirtualDisplayDevice extends DisplayDevice implements DeathRecipient {
@Override
public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
if (mInfo == null) {
mInfo = new DisplayDeviceInfo();
mInfo.name = mName;
mInfo.uniqueId = getUniqueId();
mInfo.width = mWidth;
mInfo.height = mHeight;
mInfo.refreshRate = 60;
/***部分程式碼省略***/
}
return mInfo;
}
}
}
一秒60幀,計算下來大概16.7ms一幀。
螢幕繪製
作為嚴重影響Android口碑問題之一的UI流暢性差的問題,首先在Android 4.1版本中得到了有效處理。其解決方法就是本文要介紹的Project Butter。
Project Butter對Android Display系統進行了重構,引入了三個核心元素,即VSYNC、Triple Buffer和Choreographer。其中, VSYNC是理解Project Buffer的核心。VSYNC是Vertical Synchronization(垂直同步)的縮寫,是一種在PC上已經很早就廣泛使用的技術。 可簡單的把它認為是一種定時中斷。
接下來,將圍繞VSYNC來介紹Android Display系統的工作方式。請注意,後續討論將以Display為基準,將其劃分成16ms長度的時間段, 在每一時間段中,Display顯示一幀資料(相當於每秒60幀)。時間段從1開始編號。
沒有VSYNC的情況:
由上圖可知
1.時間從0開始,進入第一個16ms:Display顯示第0幀,CPU處理完第一幀後,GPU緊接其後處理繼續第一幀。三者互不干擾,一切正常。 2.時間進入第二個16ms:因為早在上一個16ms時間內,第1幀已經由CPU,GPU處理完畢。故Display可以直接顯示第1幀。顯示沒有問題。但在本16ms期間,CPU和GPU 卻並未及時去繪製第2幀資料(注意前面的空白區),而是在本週期快結束時,CPU/GPU才去處理第2幀資料。 3.時間進入第3個16ms,此時Display應該顯示第2幀資料,但由於CPU和GPU還沒有處理完第2幀資料,故Display只能繼續顯示第一幀的資料,結果使得第1 幀多畫了一次(對應時間段上標註了一個Jank)。 4.通過上述分析可知,此處發生Jank的關鍵問題在於,為何第1個16ms段內,CPU/GPU沒有及時處理第2幀資料?原因很簡單,CPU可能是在忙別的事情(比如某個應用通過sleep 固定時間來實現動畫的逐幀顯示),不知道該到處理UI繪製的時間了。可CPU一旦想起來要去處理第2幀資料,時間又錯過了!
NSYNC的出現
為解決這個問題,Project Buffer引入了VSYNC,這類似於時鐘中斷。結果如圖所示:
由圖可知,每收到VSYNC中斷,CPU就開始處理各幀資料。整個過程非常完美。 不過,仔細琢磨圖2卻會發現一個新問題:圖2中,CPU和GPU處理資料的速度似乎都能在16ms內完成,而且還有時間空餘,也就是說,CPU/GPU的FPS(幀率,Frames Per Second)要高於Display的FPS。確實如此。由於CPU/GPU只在收到VSYNC時才開始資料處理,故它們的FPS被拉低到與Display的FPS相同。但這種處理並沒有什麼問題,因為Android裝置的Display FPS一般是60,其對應的顯示效果非常平滑。 如果CPU/GPU的FPS小於Display的FPS,會是什麼情況呢?請看下圖:
由圖可知: 1.在第二個16ms時間段,Display本應顯示B幀,但卻因為GPU還在處理B幀,導致A幀被重複顯示。 2.同理,在第二個16ms時間段內,CPU無所事事,因為A Buffer被Display在使用。B Buffer被GPU在使用。注意,一旦過了VSYNC時間點, CPU就不能被觸發以處理繪製工作了。
三級快取
為什麼CPU不能在第二個16ms處開始繪製工作呢?原因就是隻有兩個Buffer。如果有第三個Buffer的存在,CPU就能直接使用它, 而不至於空閒。出於這一思路就引出了Triple Buffer。結果如圖所示:
由圖可知: 第二個16ms時間段,CPU使用C Buffer繪圖。雖然還是會多顯示A幀一次,但後續顯示就比較順暢了。 是不是Buffer越多越好呢?回答是否定的。由圖4可知,在第二個時間段內,CPU繪製的第C幀資料要到第四個16ms才能顯示, 這比雙Buffer情況多了16ms延遲。所以,Buffer最好還是兩個,三個足矣。
以上對VSYNC進行了理論分析,其實也引出了Project Buffer的三個關鍵點: 核心關鍵:需要VSYNC定時中斷。 Triple Buffer:當雙Buffer不夠使用時,該系統可分配第三塊Buffer。 另外,還有一個非常隱祕的關鍵點:即將繪製工作都統一到VSYNC時間點上。這就是Choreographer的作用。在它的統一指揮下,應用的繪製工作都將變得井井有條。
文章到這裡就全部講述完啦,若有其他需要交流的可以留言哦~!~!
想閱讀作者的更多文章,可以檢視我 個人部落格 和公共號: