Android優化——繪製優化之android系統顯示原理(一)

1、android系統顯示原理

能夠簡單歸納爲:android應用程序把通過測量、佈局、繪製後的surface緩存數據,經過SurfaceFlinger把數據渲染到顯示屏幕上,經過android的刷新機制來刷新數據。也就是說應用層負責繪製,系統層負責渲染,經過進程間通訊把應用層須要繪製的數據傳遞到系統層服務,系統層服務經過刷新機制把數據更新到屏幕。java

android的圖形顯示系統採用的是Client/Server架構。SurfaceFlinger(Server)由C++代碼編寫。Client端代碼分爲兩部分,一部分由java提供給應用層使用的API,另外一部分則是由C++寫成的底層具體實現。android

一、基本概念

CPU: 中央處理器,它集成了運算、緩衝、控制等單元,包括繪圖功能。CPU將對象處理爲多維圖形,紋理(Bitmaps、Drawables等都是一塊兒打包到統一的Texture紋理)。緩存

GPU:一個相似於CPU的專門用來處理Graphics的處理器, 做用用來幫助加快格柵化操做,固然,也有相應的緩存數據(例如緩存已經光柵化過的bitmap等)機制。網絡

DisplayList:它至關因而從View的繪製命令到GL命令之間的「中間語言」。它記錄了繪製該View所需的所有信息,以後只要重放(replay)便可完成內容的繪製。這樣若是View沒有改動或只部分改動,即可重用或修改DisplayList,從而避免調用了一些上層代碼,提升了效率。架構

柵格化:是將圖片等矢量資源,轉化爲一格格像素點的像素圖,顯示到屏幕上。函數

FPS(Frames Per Second):表示每秒傳遞的幀數。通俗來說就是指動畫或視頻的畫面數,對應的就是APP UI界面的刷行頻率,在一個UI動畫的播放過程當中,FPS越大,界面表現越流暢,FPS越低,界面表現越卡頓。佈局

二、繪製原理

2.1 應用層

在android的每一個view繪製中有三個核心步驟:經過Measure和Layout來肯定當前須要繪製的view所在的大小和位置,經過繪製(draw)到surface,在android系統中總體繪圖源碼是在ViewRootImp類的performTraversals()方法,經過這個方法能夠看出Measuret Layout都是遞歸來獲取view的大小和位置,而且以深度做爲優先級。由此能夠看出,層級越深,元素越多,耗時也就越長。View繪製流程爲:Measure-->Layout-->Draw。動畫

2.1.一、Measure

用深度優先原則遞歸獲得全部視圖的寬、高;獲取當前View的正確寬度childWidthMeasureSpec和高度childHeightMeasureSpec以後,能夠調用它的成員函數Measure來設置它的大小。若是當前正在測量的視圖是一個容器,那麼它又會重複執行操做,直到它的全部子孫視圖大小都測量完成爲止。線程

2.1.二、Layout

用深度優先原則遞歸獲得全部視圖的位置;當前一個子view在應用程序窗口左上角的位置肯定後,再結合它在前面測量獲得的寬度和高度,就能夠徹底肯定他在應用程序窗口中的佈局。調試

2.1.三、Draw

分爲兩種繪製方式:軟件繪製(CPU)和硬件加速(GPU),其中硬件加速在android3.0開始已經全面支持,很明顯,硬件加速在UI的顯示及繪製上效率遠高於CPU繪製,但也有一些缺點:

  • 耗電:GPU的功耗比CPU高。

  • 兼容問題:某些接口和函數不支持硬件加速。

  • 內存大:使用OPenGL的接口至少須要8MB內存。

2.2系統層

2.2.1 SurfaceFlinger服務

真正把須要顯示的數據渲染到屏幕上,是經過系統級進程中的SurfaceFlinger服務來實現的。它的主要工做有:

  • 響應客戶端事件,建立Layer與客戶端的Surface創建鏈接
  • 接收客戶端數據及屬性,修改Layer屬性,如尺寸、顏色、透明度等。
  • 將建立的Layer內容刷新到屏幕上。
  • 維持Layer的序列,並對Layer最終輸出作出裁剪計算。

在android的顯示系統中使用了android的匿名共享內存:SharedClient,來實現跨進程的數據傳輸。

一、每一個應用和SurfaceFlinger之間都會建立一個SharedClient,一個應用對應一個SharedClient。

二、SharedClient包含的是SharedBufferStack的集合,每一個SharedClient中最多建立31個SharedBufferStack。

三、每一個SharedBufferStack都對應一個Surface,也就是一個Window,這意味着一個android應用程序最多能夠包含31個窗口。

四、每一個SharedBufferStack中包含兩個(低於4.1版本)或者三個(4.1及以上版本)緩衝區,即後面顯示刷新機制中提到的雙緩衝和三重緩衝技術。

最後總起來顯示總體流程分三個模塊:應用層繪製到緩存區;SurfaceFlinger把緩存區數據渲染到屏幕;因爲是兩個不一樣的進程,因此使用android的匿名共享內存SharedClient緩存須要顯示的數據來達到目的。

繪製過程首先是CPU準備數據,經過Driver層把數據交給GPU渲染,其中CPU負責Measure、Layout、Record、Execute的數據計算工做,GPU負責柵格化、渲染。因爲圖形API不容許CPU直接與GPU通訊,而是經過中間的一個圖形驅動層(Graphics Driver)來鏈接兩部分。圖形驅動維護了一個隊列,CPU把DisplayList添加到隊列中,GPU從這個隊列取出數據進行繪製,最終纔在顯示屏上顯示出來。

2.2.2 60Hz 和 16 ms

12 FPS——因爲人類眼睛的特殊生理結構,若是所看畫面之幀率高於每秒約10-12幀的時候,就會認爲是連貫的。

24 FPS——有聲電影的拍攝及播放幀率均爲每秒24幀,對通常人而言已算可接受。

60 FPS—— 在與手機交互過程當中,如觸摸和反饋60幀如下人是能感受出來的。60幀以上不能察覺變化,當幀率低於60FPS 時感受的畫面的卡頓和遲滯現象。

因爲人體眼睛生理結構的特殊性,因而這就是60Hz的由來,而1000ms/60=16.66ms這就是16ms的由來。

三、刷新機制

Android系統每隔16ms發出VSync信號,觸發對UI進行渲染(即每16ms顯示一幀),若是每次渲染都成功這樣就可以達到流暢的畫面所須要的60fps,爲了可以實現60fps,這意味着計算渲染的大多數操做都必須在16ms內完成。若是某個操做花費時間是24ms,系統在獲得VSync信號時就沒法進行正常渲染,這樣就發生了丟幀現象。那麼用戶在32ms內看到的會是同一幅畫面,從而感受卡頓。有不少緣由能夠致使CPU或者GUP負載太重從而出現丟幀現象:多是Layout太過複雜,沒法在16ms內完成渲染;多是UI上有層疊太多的繪製單元;還有多是動畫執行次數過多。

在android4.1版本中有效處理了UI流暢性差的問題。其解決方法即在4.1版本推出的Project Buffer。Project Buffer對android Display系統進行了重構,引入三個核心元素:VSync、Triple Buffer和Choreographer。其中VSync是理解Project Buffer的核心,,簡單地能夠把它認爲是一種定時中斷技術。Choreographer起調試的做用,將繪製工做統一到VSync的某個時間點上,使應用的繪製工做有序。

雙緩衝:顯示內容的數據內存。咱們知道在Linux上一般使用Framebuffer來作顯示輸出,當用戶進程更新Framebuffer中的數據後,顯示驅動會把Framebuffer中每一個像素點的值更新到屏幕,可是這樣會有一個問題,若是上一幀數據還沒顯示完,Framebuffer中的數據又更新了,就會帶來殘影問題,給用戶的直觀感受就會有閃爍感,因此廣泛採用了雙緩衝技術。雙緩衝意味着要使用兩個緩衝區(在SharedBufferStack中),其中一個稱爲Front Buffer,另外一個稱爲Back Buffer。UI老是先在Back Buffer中繪製,而後再和Front Buffer交換,渲染到顯示設備中。即只有當另外一個buffer的數據準備好後,經過io_ctrl來通知顯示設備切換buffer。

VSync(Verical Synchronization):垂直同步,從前面的雙緩衝介紹中能夠了解到,只有當另外一個buffer準備好後,才能通知刷新 ,這就須要CPU以主動查詢的方式來保證數據是否準備好,由於這種機制效率很低,因此引入了VSync。能夠簡單地把它認爲是一種定時中斷,一旦收到VSync中斷,CPU就開始處理各幀數據。 Choreographer:收到VSync信號時,調用 用戶設置的回調函數。一共有如下三種類型的回調:

  • CALLBACK_INPUT:優先級最高,與輸入事件有關。

  • CALLBACK_ANIMATION:第二優先級,與動畫有關。

  • CALLBACK_TRAVERSAL:最低優先級,與UI控件繪製有關。

接下來經過時序圖來分析刷新的過程,這些時序圖是2018年Google I/O講解新的顯示系統提供的,圖3.1所示的時序圖有三個元素:Display(顯示設備),CPU-CPU準備數據,GPU-GPU準備數據。最下面的顯示時間,根據理想的60FPS,以16ms爲一個顯示週期。

圖3.1    沒有Vsync信息的刷新

(1)沒有VSnyc信號同步

咱們以16ms爲單位來進行分析:

1)從第一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幀的數據,結果使得第1幀多畫了一次(對應時間段上標註了一個Jank),這就致使錯過了顯示第2幀。

經過上述分析可知,在第二個16ms時,發生Jank的關鍵問題在於,爲什麼在第1個16ms段內,CPU/GPU沒有及時處理第2幀數據?從第2個16ms開始有一段空白的時間,能夠說明緣由所在,那就是CPU多是在忙別的事情 ,不知道該處處理UI繪製的時間了。可CPU一旦想起來要去處理第2幀數據,時間又錯過了。爲解決這個問題,4.1版本推出了Project Buffer,核心目的就是解決刷新不一樣步的問題。

(2)有VSync信號同步

加入VSync後,從圖3.2能夠看到,一旦收到VSync中斷,CPU就開始處理各幀的數據。大部分的android顯示設備刷新率是60Hz,這也就意味着第一幀最多隻能有1/60=16ms左右的準備時間。假如CPU/GPU的FPS高於這個值,顯示效果將更好。可是,這時又出現一個新問題:CPU和GPU處理數據的速度都能在16ms內完成,並且還有時間空餘,但必須等到VSync信號到來後,才處理下一幀數據,所以CPU/GPU的FPS被拉低到與Display的FPS相同。

從圖3.3採用雙緩衝區的顯示效果來看:在雙緩衝下,CPU/GPU的FPS大於刷新頻率同時採用了雙緩衝技術以及VSync,能夠看到整個過程仍是至關不錯的,雖然CPU/GPU處理所用的時間時短時長,但整體來講都在16ms內,於是不影響顯示效果。A和B分別表明兩個緩衝區,它們不斷交換來正確顯示畫面。但若是CPU/GPU的FPS小於DIsplay的FPS,狀況又不一樣了,如圖3.4所示。

圖3.2    有VSnyc的繪製

圖3.3    雙緩衝下的時序圖

圖3.4    雙緩衝下CPU/GPU的FPS小於刷新頻率的時序圖

從圖3.4能夠看到,當CPU/GPU的處理時間超過16ms時,第一個VSync就已經到來,但緩衝區B中的數據卻尚未準備好,這樣就只能繼續顯示以前A緩衝區中的內容。然後面B完成後,又由於尚未VSync信號,CPU/GPU這個時候只能等待下一個VSync的來臨纔開始處理下一幀數據。所以在整個過程當中,有一大段時間被浪費。總結這段話就是:

1)在第2個16ms時間段內,Display本就顯示B幀,但由於GPU還在處理B幀,致使A幀被重複顯示。

2)同理,在第動起來個16ms時間段內,CPU無所事事,由於A Buffer由Display的使用。B Buffer由GPU使用。注意,一旦過了VSync時間點,CPU就不能被觸發以及處理繪製工做了。

爲何CPU不能在第2個16ms時間處即VSync到來就開始工做呢?很明顯,緣由就是隻有兩個Buffer。若是有第三個Buffer存在,CPU就能夠開始工做,而不至於空閒。因而在android4.1之後,引出了第三個緩衝區:Triple Buffer。Triple Buffer利用CPU/GPU的空閒等待時間提早準備好數據,並不必定會使用。

引入Triple Buffer後的刷新時序如圖3.5所示。

圖3.5    使用Triple Buffer時序圖

在第二個16ms時間段,CPU使用C Buffer繪圖。雖然仍是會多顯示一次A幀,但後續顯示就比較順暢了。是否是Buffer越多越好呢?回答是否認的。由圖3.5可知,在第二個時間段內,CPU繪製的和C幀數據要到第四個16ms才顯示,這比雙緩存狀況多了16ms延遲。因此緩衝區不是越多越好,要作到平衡到最佳效果。

從以上分析來看,andorid系每戶在顯示機制上解決了android UI顯示不流暢的問題,而且從Google 2012年I/O大會給出的視頻來看,其效果也達到了預期。但實際在應用開發過程當中仍然存在卡頓的現象。由於VSync中斷處理的線程優先級必定要最高,不然即便接收到VSync中斷,不能及時處理,也是徒勞無功。

四、卡頓的根本緣由

那卡頓的根本緣由是什麼呢,從android系統的顯示原理中能夠看到,影響繪製的根本緣由有如下兩方面:

繪製任務過重,繪製一幀內容耗時太長。 主線程太忙了,致使VSync信號來時尚未準備好數據致使丟幀。 耗時太長,須要從UI佈局和繪製上來具體分析。這裏主要討論下第二個方面。咱們知道全部的繪製工做都是由主線程,也就是UI線程來負責,主線程的關鍵職責是處理用戶交互,在屏幕上繪製像素,並進行加載顯示相關的數據。在android應用開發中 ,特別須要避免任何阻礙主線程的事情,這樣應用程序才能保持對用戶操做的即時響應。

在實際的開發過程當中,咱們須要知道主線程應該作什麼,總結起來主線程主要作如下幾個方面工做:

  • UI生命週期控制
  • 系統事件處理
  • 消息處理
  • 界面佈局
  • 界面繪製
  • 界面刷新 除了這些之外,儘可能避免將其餘處理放到主線程中,特別是複雜的數據計算和網絡請求。
相關文章
相關標籤/搜索