# 問題
- Q1:但是大伙想過沒有,這個 16.6刷新一次屏幕到底是什么意思呢?是指每隔 16.6ms 調用 onDraw() 繪制一次么?
- Q2:如果界面一直保持沒變的話,那么還會每隔 16.6ms 刷新一次屏幕么?
- Q3:界面的顯示其實就是一個 Activity 的 View 樹里所有的 View 都進行測量、布局、繪制操作之后的結果呈現,那么如果這部分工作都完成后,屏幕會馬上就刷新么?
- Q4:網上都說避免丟幀的方法之一是保證每次繪制界面的操作要在 16.6ms 內完成,但如果這個 16.6ms 是一個固定的頻率的話,請求繪制的操作在代碼里被調用的時機是不確定的啊,那么如果某次用戶點擊屏幕導致的界面刷新操作是在某一個 16.6ms 幀快結束的時候,那么即使這次繪制操作小于 16.6 ms,按道理不也會造成丟幀么?這又該如何理解?
- Q5:大伙都清楚,主線程耗時的操作會導致丟幀,但是耗時的操作為什么會導致丟幀?它是如何導致丟幀發生的?
# 屏幕展示的顏色數據
>- 在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`類中我們有一個方法獲取屏幕刷新速率:
```java
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的作用。在它的統一指揮下,應用的繪制工作都將變得井井有條。
Android 平臺提供了三類動畫,一類是 Tween 動畫-Animation,即通過對場景里的對象不斷做圖像變換 ( 平移、縮放、旋轉 ) 產生動畫效果;第二類是 Frame 動畫,即順序播放事先做好的圖像,跟電影類似。最后一種就是3.0之后才出現的屬性動畫PropertyAnimator(在下文我們講幀動畫和補間動畫統一稱為View動畫)。如果有人對ViewGroup內部View使用過View動畫的還知道有layout-animation。
大家對這三種動畫基本都能熟練的使用,那么…...?
- 想知道動畫與界面渲染與屏幕刷新有著什么樣的關系?
- 想知道屬性動畫為什么會發生內存泄露么?
因為本文章中會有一些屏幕刷新、Vsync信號相關的知識點,讀過我寫的 [Android的16ms和垂直同步以及三重緩存](https://www.jianshu.com/p/3750db831aca) 和[Android系統的編舞者Choreographer](https://www.jianshu.com/p/fb645ea98474) 這兩篇文章的同學會可能會更容易了解本文章。
接下來拿起我們的鍵盤、鼠標和顯示器,我們將探索從Android源碼(android-23)的角度去探索動畫的實現~!
- 寫在前面的話
- Java
- 基礎
- Double的比較
- 小數怎么用二進制表示
- 多線程
- 并發和并行
- 線程池
- 線程池背景
- 線程池構造
- 任務阻塞隊列
- Flutter
- 基礎知識
- Dart基礎
- Android
- 項目架構
- View
- 非UI線程更新View
- AlarmManager
- 對比postDelaryed和Timer
- Bitmap
- 加載100M的圖片卻不撐爆內存
- Bitmap壓縮
- Bitmap局部解碼
- 計算圖片的內存占用
- Android動畫
- Android動畫類型
- Android動畫原理
- 屬性動畫
- 幀動畫
- 補間動畫
- 使用動畫的注意事項
- Android新特性
- 權限組
- Android23(Marshmallow)-6.0
- Android24(Nougat)-7.0
- Android26(Oreo)-8.0
- Android28(Pie)-9.0
- Android29(Q)-10.0
- AndroidX遷移
- Kotlin
- 關鍵字
- Kotlin操作符
- CoroutineScope
- Flow
- CoroutineException