除了Bug,最讓你頭疼的問題是什么?單身?禿頭?996?面試造火箭,工作擰螺絲?
作為安卓開發者,除了Bug,經常會碰到下面這些問題:
應用卡頓,丟幀,屏幕畫面撕裂,操作界面刷新緩慢,UI不美觀,布局混亂…這些問題頻發的話,年后可能就不用來了。
開發App的時候,你是否會覺得界面卡頓?尤其是自定義View的時候。
Android 應用的卡頓、丟幀等,這些影響用戶體驗的因素絕大部分都與 16ms 這個值有關。Android 設備的刷新率也是 60Hz,Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,如果超過了16ms,我們則認為發生了卡頓。
二、顯示系統基礎知識
在一個典型的顯示系統中,一般包括CPU、GPU、Display三個部分, CPU負責計算幀數據,把計算好的數據交給GPU,GPU會對圖形數據進行渲染,渲染好后放到buffer(圖像緩沖區)里存起來,然后Display(屏幕或顯示器)負責把buffer里的數據呈現到屏幕上。如下圖:
2.1 基礎概念
-
屏幕刷新頻率 一秒內屏幕刷新的次數(一秒內顯示了多少幀的圖像),單位 Hz(赫茲),如常見的 60 Hz。刷新頻率取決于硬件的固定參數(不會變的)。
-
逐行掃描 顯示器并不是一次性將畫面顯示到屏幕上,而是從左到右邊,從上到下逐行掃描,順序顯示整屏的一個個像素點,不過這一過程快到人眼無法察覺到變化。以 60 Hz 刷新率的屏幕為例,這一過程即 1000 / 60 ≈ 16ms。
-
幀率 (Frame Rate) 表示 GPU 在一秒內繪制操作的幀數,單位 fps。例如在電影界采用 24 幀的速度足夠使畫面運行的非常流暢。而 Android 系統則采用更加流程的 60 fps,即每秒鐘GPU最多繪制 60 幀畫面。幀率是動態變化的,例如當畫面靜止時,GPU 是沒有繪制操作的,屏幕刷新的還是buffer中的數據,即GPU最后操作的幀數據。
-
畫面撕裂(tearing) 一個屏幕內的數據來自2個不同的幀,畫面會出現撕裂感,如下圖
2.2 雙緩存
2.2.1 畫面撕裂 原因
屏幕刷新頻是固定的,比如每16.6ms從buffer取數據顯示完一幀,理想情況下幀率和刷新頻率保持一致,即每繪制完成一幀,顯示器顯示一幀。但是CPU/GPU寫數據是不可控的,所以會出現buffer里有些數據根本沒顯示出來就被重寫了,即buffer里的數據可能是來自不同的幀的, 當屏幕刷新時,此時它并不知道buffer的狀態,因此從buffer抓取的幀并不是完整的一幀畫面,即出現畫面撕裂。
簡單說就是Display在顯示的過程中,buffer內數據被CPU/GPU修改,導致畫面撕裂。
2.2.2 雙緩存
那咋解決畫面撕裂呢? 答案是使用 雙緩存。
由于圖像繪制和屏幕讀取 使用的是同個buffer,所以屏幕刷新時可能讀取到的是不完整的一幀畫面。
雙緩存,讓繪制和顯示器擁有各自的buffer:GPU 始終將完成的一幀圖像數據寫入到 Back Buffer,而顯示器使用 Frame Buffer,當屏幕刷新時,Frame Buffer 并不會發生變化,當Back buffer準備就緒后,它們才進行交換。如下圖:
2.2.3 VSync
問題又來了:什么時候進行兩個buffer的交換呢?
假如是 Back buffer準備完成一幀數據以后就進行,那么如果此時屏幕還沒有完整顯示上一幀內容的話,肯定是會出問題的。看來只能是等到屏幕處理完一幀數據后,才可以執行這一操作了。
當掃描完一個屏幕后,設備需要重新回到第一行以進入下一次的循環,此時有一段時間空隙,稱為VerticalBlanking Interval(VBI)。那,這個時間點就是我們進行緩沖區交換的最佳時間。因為此時屏幕沒有在刷新,也就避免了交換過程中出現 screen tearing的狀況。
VSync(垂直同步)是VerticalSynchronization的簡寫,它利用VBI時期出現的vertical sync pulse(垂直同步脈沖)來保證雙緩沖在最佳時間點才進行交換。另外,交換是指各自的內存地址,可以認為該操作是瞬間完成。
所以說V-sync這個概念并不是Google首創的,它在早年的PC機領域就已經出現了。
三、Android屏幕刷新機制
3.1 Android4.1之前的問題
具體到Android中,在Android4.1之前,屏幕刷新也遵循 上面介紹的 雙緩存+VSync 機制。如下圖:
以時間的順序來看下將會發生的過程:
- Display顯示第0幀數據,此時CPU和GPU渲染第1幀畫面,且在Display顯示下一幀前完成
- 因為渲染及時,Display在第0幀顯示完成后,也就是第1個VSync后,緩存進行交換,然后正常顯示第1幀
- 接著第2幀開始處理,是直到第2個VSync快來前才開始處理的。
- 第2個VSync來時,由于第2幀數據還沒有準備就緒,緩存沒有交換,顯示的還是第1幀。這種情況被Android開發組命名為“Jank”,即發生了丟幀。
- 當第2幀數據準備完成后,它并不會馬上被顯示,而是要等待下一個VSync 進行緩存交換再顯示。
所以總的來說,就是屏幕平白無故地多顯示了一次第1幀。
原因是 第2幀的CPU/GPU計算 沒能在VSync信號到來前完成 。
我們知道,雙緩存的交換 是在Vsyn到來時進行,交換后屏幕會取Frame buffer內的新數據,而實際 此時的Back buffer 就可以供GPU準備下一幀數據了。 如果 Vsyn到來時 CPU/GPU就開始操作的話,是有完整的16.6ms的,這樣應該會基本避免jank的出現了(除非CPU/GPU計算超過了16.6ms)。 那如何讓 CPU/GPU計算在 Vsyn到來時進行呢?
3.2 drawing with VSync
為了優化顯示性能,Google在Android 4.1系統中對Android Display系統進行了重構,實現了Project Butter(黃油工程):系統在收到VSync pulse后,將馬上開始下一幀的渲染。即一旦收到VSync通知(16ms觸發一次),CPU和GPU 才立刻開始計算然后把數據寫入buffer。如下圖:
CPU/GPU根據VSYNC信號同步處理數據,可以讓CPU/GPU有完整的16ms時間來處理數據,減少了jank。
一句話總結,VSync同步使得CPU/GPU充分利用了16.6ms時間,減少jank。
問題又來了,如果界面比較復雜,CPU/GPU的處理時間較長 超過了16.6ms呢?如下圖:
- 在第二個時間段內,但卻因 GPU 還在處理 B 幀,緩存沒能交換,導致 A 幀被重復顯示。
- 而B完成后,又因為缺乏VSync pulse信號,它只能等待下一個signal的來臨。于是在這一過程中,有一大段時間是被浪費的。
- 當下一個VSync出現時,CPU/GPU馬上執行操作(A幀),且緩存交換,相應的顯示屏對應的就是B。這時看起來就是正常的。只不過由于執行時間仍然超過16ms,導致下一次應該執行的緩沖區交換又被推遲了——如此循環反復,便出現了越來越多的“Jank”。
為什么 CPU 不能在第二個 16ms 處理繪制工作呢?
原因是只有兩個 buffer,Back buffer正在被GPU用來處理B幀的數據, Frame buffer的內容用于Display的顯示,這樣兩個buffer都被占用,CPU 則無法準備下一幀的數據。 那么,如果再提供一個buffer,CPU、GPU 和顯示設備都能使用各自的buffer工作,互不影響。
3.3 三緩存
三緩存就是在雙緩沖機制基礎上增加了一個 Graphic Buffer 緩沖區,這樣可以最大限度的利用空閑時間,帶來的壞處是多使用的一個 Graphic Buffer 所占用的內存。
-
第一個Jank,是不可避免的。但是在第二個 16ms 時間段,CPU/GPU 使用 第三個 Buffer 完成C幀的計算,雖然還是會多顯示一次 A 幀,但后續顯示就比較順暢了,有效避免 Jank 的進一步加劇。
-
注意在第3段中,A幀的計算已完成,但是在第4個vsync來的時候才顯示,如果是雙緩沖,那在第三個vynsc就可以顯示了。
三緩沖有效利用了等待vysnc的時間,減少了jank,但是帶來了延遲。 所以,是不是 Buffer 越多越好呢?這個是否定的,Buffer 正常還是兩個,當出現 Jank 后三個足以。
以上就是Android屏幕刷新的原理了。
小結
有了這么多優秀的開發工具,可以做出更高質量的Android應用。
當然了,“打鐵還需自身硬”,想要寫出優秀的代碼,最重要的一點還是自身的技術水平,不然用再好的工具也不能發揮出它的全部實力。
在這里我也分享一份大佬自己收錄整理的Android學習PDF+架構視頻+面試文檔+源碼筆記,還有高級架構技術進階腦圖、Android開發面試專題資料,高級進階架構資料這些都是我閑暇還會反復翻閱的精品資料。在腦圖中,每個知識點專題都配有相對應的實戰項目,可以有效的幫助大家掌握知識點。
總之也是在這里幫助大家學習提升進階,也節省大家在網上搜索資料的時間來學習,也可以分享給身邊好友一起學習
oid開發面試專題資料,高級進階架構資料**這些都是我閑暇還會反復翻閱的精品資料。在腦圖中,每個知識點專題都配有相對應的實戰項目,可以有效的幫助大家掌握知識點。
總之也是在這里幫助大家學習提升進階,也節省大家在網上搜索資料的時間來學習,也可以分享給身邊好友一起學習
如果你有需要的話,可以點擊這里領取