Android圖形顯示系統(一)

良秋發表於2017-05-05

簡介

本文講解的內容是Android4.1以後的系統機制,將從整體上分析Android圖形顯示系統的結構,不深入分析每一層內部的程式碼實現,更多的是使用流程圖和結構圖來讓大家理解Android是如何繪製、合成圖形並顯示到螢幕上。

轉載請註明出處:juejin.im/post/590a91…

本文將從三個層次進行講解,大致如下圖:

Android圖形顯示系統(一)
大體流程

可以理解為上層生產,下層消費模型。

其中每一層之間的資料傳遞是使用Buffer(圖形緩衝區)作為載體:

Android圖形顯示系統(一)
圖形資料的載體

這裡的緩衝區,大家可以理解為帶有寬高和畫素密度的記憶體區塊。

1. 從下層往上層理解

1.1 螢幕

螢幕上的內容,是從硬體幀緩衝區讀取的,大致讀取過程為:從Buffer的起始地址開始,從上往下,從左往右掃描整個Buffer,將內容對映到螢幕上:

Android圖形顯示系統(一)
螢幕與幀緩衝區關係圖1

當然,螢幕上的內容需要需要不斷的更新,如果在同一個Buffer進行讀取和寫入(合成)操作,將會導致螢幕顯示多幀內容。所以硬體層除了提供一個Buffer用於螢幕顯示,還提供了一個Buffer用於後臺的圖形合成,也就是我們常說的雙緩衝:

Android圖形顯示系統(一)
螢幕與幀緩衝區關係圖2

上圖中包含兩個緩衝區:
前緩衝區:用來顯示內容到螢幕的幀緩衝區
後緩衝區:用於後臺合成下一幀圖形的幀緩衝區

假設前一幀顯示完畢,後一幀準備好了,螢幕將會開始讀取下一幀的內容,也就是開始讀取上圖中的後緩衝區的內容:

Android圖形顯示系統(一)
螢幕與幀緩衝區關係圖途2

此時,前後緩衝區進行一次角色互換,之前的後緩衝區變為前緩衝區,進行圖形的顯示,之前的前緩衝區則變為後緩衝區,進行圖形的合成。

然而,理想很豐滿,現實很骨感,上面假設“當前一幀顯示完畢,後一幀準備好了”的情況,在現實中這兩個事件並非同時完成。那麼,螢幕掃描緩衝區的速度和系統合成幀的速度之間有什麼關係呢,帶著這個疑惑我們看看下面兩個概念:
螢幕重新整理率(HZ):代表螢幕在一秒內重新整理螢幕的次數,Android手機一般為60HZ(也就是1秒重新整理60幀,大約16.67毫秒重新整理1幀)
系統幀速率(FPS):代表了系統在一秒內合成的幀數,該值的大小由系統演算法和硬體決定。

我們用以下兩個假設來分析兩者的關係:
① 螢幕重新整理速率比系統幀速率快
此時,在前緩衝區內容全部對映到螢幕上之後,後緩衝區尚未準備好下一幀,螢幕將無法讀取下一幀,所以只能繼續顯示當前一幀的圖形,造成一幀顯示多次,也就是卡頓。
② 系統幀速率比螢幕重新整理率快
此時,螢幕未完全把前緩衝區的一幀對映到螢幕,而系統已經在後緩衝區準備好了下一幀,並要求讀取下一幀到螢幕,將會導致螢幕上半部分是上一幀的圖形,而下半部分是下一幀的圖形,造成螢幕上顯示多幀,也就是螢幕撕裂。

上面兩種情況,都會導致問題,根本原因就是兩個緩衝區的操作速率不一致,解決辦法就是讓螢幕控制前後緩衝區的切換,讓系統幀速率配合螢幕重新整理率的節奏。

那麼螢幕是如何控制這個節奏的呢?

垂直同步(VSync):當螢幕從緩衝區掃描完一幀到螢幕上之後,開始掃描下一幀之前,發出的一個同步訊號,該訊號用來切換前緩衝區和後緩衝區。

通過上面的分析可以看出,螢幕的顯示節奏是固定的,作業系統需要配合螢幕的顯示,在固定的時間內準備好下一幀,以供螢幕進行顯示。兩者通過VSync訊號來實現同步。

關於螢幕這一塊的知識點講解到這,不再深入分析,接下來我們將會了解後緩衝區的圖形合成者。

1.2 SurfaceFlinger-圖形合成者

如果說螢幕是消費者,那麼SurfaceFlinger相對螢幕來說就是生產者,其具有如下特性:

  • 作為上層應用的消費者,硬體層的生產者。
  • 負責圖形的合成
  • 和ActivityManagerService一樣,是一個系統服務

為了更好的理解SurfaceFlinger這個服務的工作內容,以及他是如何做到一個承上啟下的作用,我們通過下面的這個介面分析:

Android圖形顯示系統(一)
這裡寫圖片描述

介面很簡單,拆開來看,包含微信、懸浮工具箱、通知欄、底部虛擬按鍵欄:

Android圖形顯示系統(一)
介面對應的surface

我們可以先這樣理解上面這幅圖,上層每一個介面,其實都對應SufaceFlinger裡的一個Surface物件,上層將自己的內容繪製在對應的Surface內,接著,SufaceFlinger需要將所有上層對應的Surface內的圖形進行合成,具體看下圖:

Android圖形顯示系統(一)
SurfaceFlinger合成過程

沒錯,SurfaceFlinger就是將多個Surface裡的內容進行合成,最後提交到螢幕的後緩衝區,等待螢幕的下一個垂直同步訊號的到來,再顯示到螢幕上。

我們會發現SufaceFlinger通過螢幕後緩衝區與螢幕建立聯絡。同時通過Surface與上層建立聯絡。從而起到一個承上啟下的作用,是Android圖形系統結構中的關鍵組成部分。

為了繼續往上層講,我們需要了解什麼是Surface:

  • 對應上層的一個Window(對話方塊、Activity、狀態列)
  • 作為上層圖形繪製的畫板
  • Canvas是畫筆,上層通過呼叫Canvas的API向Surface上繪製圖形
  • Surface內部存在多個緩衝區,形成一個BufferQueue

如果說SurfaceFinger是圖形的合成者,那麼圖形的提供者就是上層。文章一開始就提到,圖形的傳遞是通過Buffer作為載體,Surface是對Buffer的進一步封裝,也就是說Surface內部具有多個Buffer供上層使用,如何管理這些Buffer呢?請看下面這個模型:

Android圖形顯示系統(一)
BufferQueue

Surface內部提供一個BufferQueue,與上層和SurfaceFlinger形成一個生產者消費者模型,上層對應Producer,SurfaceFlinger對應Consumer。三者通過Buffer產生聯絡,每個Buffer都有四種狀態:

  • Free:可被上層使用
  • Dequeued:出列,正在被上層使用
  • Queued:入列,已完成上層繪製,等待SurfaceFlinger合成
  • Acquired:被獲取,SurfaceFlinger正持有該Buffer進行合成

Buffer的一次轉移過程大致為:

  1. 從BufferQueue轉移到上層
  2. 上層繪製完成再放回BufferQueue
  3. 接著SurfaceFlinger再拿去合成
  4. 最後又放回BufferQueue

如此迴圈,形成一個Buffer被迴圈使用的過程。

關於SurfaceFlinger以及SurfaceFlinger與上層建立聯絡的Surface講解完了,接下來看看上層是如何將圖形繪製到Surface的Buffer中。

1.3 上層繪圖

上層繪圖的大體流程見下圖:

Android圖形顯示系統(一)
上層繪圖

之前有說到,Surface裡的Buffer作為上層的畫板,Canvas作為畫筆,通過呼叫Canvas的API完成圖形的繪製,上層通過呼叫draw方法來呼叫Canvas的API,當然這裡的draw方法並沒有真正的將圖形繪製到緩衝區,而是記錄了一下繪製命令,具體需要了解DisplayList相關只是,後面會對其進行分析。

從流程上看:

  1. 測量View的寬高(Measure)
  2. 設定View的寬高位置(Layout)
  3. 建立顯示列表,並執行繪製(Draw)
  4. 生成多邊形和紋理
  5. 對多邊形和紋理進行柵格化操作

從執行者的角度看:

  1. CPU:Measure,Layout,紋理和多邊形生成,傳送紋理和多邊形到GPU
  2. GPU:將CPU生成的紋理和多邊形進行柵格化以及合成

上面說的的紋理和多邊形還有柵格化以及合成,這裡不做具體的講解,需要了解的是圖形的繪製流程需要經過這些操作。從上面的分析可以看出,上層繪製圖形時需要經過CPU計算,再經過GPU計算。

經過上面的分析,整個Android的圖形繪製大體流程已經分析完成,接下來將會分析一些流程的具體實現,分析的內容包括:

  • Android 4.1 加入的VSync訊號同步到上層以及三緩衝
  • 從上層往下層具體分析每一步流程

2. VSync以及三緩衝

2.1 Drawing Without VSync

從上面的講解可以看出,整個繪製流程的節奏,分成兩個生產者消費者模型,一個由螢幕和SurfaceFlinger構成,另一個由SurfaceFlinger和上層應用構成,具體流程可以用下圖來描述:

Android圖形顯示系統(一)
drawing without vsync

其中:

  1. CPU和GPU代表上層的繪製執行者
  2. Composite代表的是SurfaceFlinger對多個Surface的合成
  3. Background Buffer和Front Buffer分別代表的是硬體幀緩衝區中的前緩衝和後緩衝
  4. 螢幕掃描完一幀之後,會發出VSync訊號來切換並顯示下一幀

上面的流程中,存在一個問題,螢幕的VSync訊號只是用來控制幀緩衝區的切換,並未控制上層的繪製節奏,也就是說上層的生產節奏和螢幕的顯示節奏是脫離的:

Android圖形顯示系統(一)
drawing without vsync

上圖中,橫軸表示時間,縱軸表示Buffer的使用者,每個長方形表示Buffer的使用,長方形的寬度代表使用時長,VSync代表垂直同步訊號,兩個VSync訊號之間間隔16.6ms。此圖描述了Android在4.1系統版本之前,上層的繪圖流程在沒有VSync訊號的時候,出現的繪製問題。

我們從時間為0開始看,當前螢幕顯示第0幀,上層CPU開始計算第1幀的紋理,計算完成後,交由GPU進行柵格化。當下一個垂直同步訊號到來,螢幕顯示下一幀,這時候,上層CPU並未馬上開始準備下一幀,而當CPU開始準備下一幀的時候已經太晚了,下一個VSync訊號來臨的時候,GPU未能繪製完第二幀的處理,導致螢幕再次顯示上一幀,造成卡頓:

Android圖形顯示系統(一)
drawing without vsync

2.2 Drawing With VSync

因為上層不知道VSync訊號已經發出,導致上層未能開始CPU的計算。google在Android 4.1系統中加入了上層接收垂直同步訊號的邏輯,大致流程如下:

Android圖形顯示系統(一)
draw with vsync

也就是說,螢幕在顯示完一幀後,發出的垂直同步除了通知幀緩衝區的切換之外,該訊息還會傳送到上層,通知上層開始繪製下一幀。

那麼,上層是如何接受這個VSync訊息的呢?

2.2.1 Choreographer VSync訊號的上層接收者

Google為上層設計了一個Choreographer類,翻譯成中文是“編舞者”,是希望通過它來控制上層的繪製(舞蹈)節奏。

首先看看Choreographer的類圖:

Android圖形顯示系統(一)
Choreographer類圖

可以發現,Choreographer需要向SurfaceFlinger來註冊一個VSync訊號的接收器DisplayEventReceiver。同時在Choreographer的內部維護了一個CallbackQueue,用來儲存上層關心VSync訊號的元件,包括ViewRootImpl,TextView,ValueAnimator等。

再看看上層接收VSync的時序圖:

Android圖形顯示系統(一)
上層接收VSync時序圖

知道了Choreographer是上層用來接收VSync的角色之後,我們需要進一步瞭解VSync訊號是如何控制上層的繪製的:

Android圖形顯示系統(一)
上層VSync與繪製相結合的時序圖

一般,上層需要繪製新的UI都是因為View的requestLayout或者是invalidate方法被呼叫觸發的,我們以這個為起點,跟蹤上層View的繪製流程:

  1. requestLayout或者invalidate觸發更新檢視請求
  2. 更新請求傳遞到ViewRootImpl中,ViewRootImpl向主執行緒MessageQueue中加入一個阻塞器,該阻塞器將會攔截所有同步訊息,也就是說此時,我們再通過Handler向主執行緒MessageQueue傳送的所有Message都將無法被執行。
  3. ViewRootImpl向Choreographer註冊下一個VSync訊號
  4. Choreographer通過DisplayEventReceiver向framework層註冊下一個VSync訊號
  5. 當底層產生下一個VSync訊息時,該訊號將會傳送給DisplayEventReceiver,最後傳遞給Choreographer
  6. Choreographer收到VSync訊號之後,向主執行緒MessageQueue傳送了一個非同步訊息,我們在第二步提到,ViewRootImpl向MessageQueue傳送了一個同步訊息阻塞器。這裡Choreographer傳送的非同步訊息,是不會被阻塞器攔截的。
  7. 最後,非同步訊息的執行者是ViewRootImpl,也就是真正開始繪製下一幀了

至此,底層的VSync控制上層的邏輯就解釋完了,此時上層繪製圖形的流程與VSync訊號的關係可以用下圖表示:

Android圖形顯示系統(一)
draw with vsync

時間從螢幕顯示第0幀開始,CPU開始準備第1幀圖形的處理,好了之後交給GPU進行處理,在上層收到下一個VSync之後,CPU立馬開始第2幀的處理,上層繪圖的節奏就和VSync訊號保持一致了,整個繪圖非常流暢。

然而,理想很豐滿,現實很骨感,如果CPU和GPU沒能在下一個VSync訊號到來之前完成下一幀的繪製工作,又會是怎麼樣的呢?

Android圖形顯示系統(一)
parallel processing and double buffering

還是從螢幕顯示第A幀開始,時間進入第一個16.6ms,CPU和GPU合成第B幀,當下一個VSync訊號到來的時候,GPU未能及時完成第B幀的繪製,此時,GPU佔有一個Surface裡的Buffer,而同時SurfaceFlinger又持有一個Buffer用於合成顯示下一幀到螢幕,這樣的話,就導致Surface裡的兩個緩衝區都被佔用了。此時SurfaceFlinger只能使用第A幀已經準備好的Buffer來合成,GPU繼續在另一個緩衝區中合成第B幀,此時CPU無法開始下一幀的合成,因為緩衝區用完了。另外一個不好的事情是CPU只有在VSync訊號來的時候才開始繪製下一幀,也是就是說在第二個16.6ms時間內,CPU一直處於空閒狀態,未進行下一幀的計算。
只有等到第二個VSync訊號來了之後,CPU才開始在繪製下一幀。如果CPU和GPU需要合成的圖形太多,將會導致連續性的卡頓,如果CPU和GPU大部分時候都無法在16.6ms完成一幀的繪製,將會導致連續的卡頓現象。

彆著急,請看看Google的解決方案。

2.3 parallel processing and triple buffering

沒錯,就是加入第三個Buffer,CPU和GPU還有SurfaceFlinger各佔一個Buffer,並行處理圖形:

Android圖形顯示系統(一)
parallel processing and triple buffering

從上圖可以看出,在第一個VSync到來時,儘管SurfaceFlinger佔了一個Buffer,GPU又佔了一個Buffer,CPU仍然可以在第三個Buffer中開始下一幀的計算,整個顯示過程就開始時卡頓了一幀,之後都是流暢的。

當然系統並非一直開啟三個Buffer,因為Buffer是需要消耗資源的,並且,我們會發現,上圖中,GPU處理好的圖形,需要跨越兩個VSync訊號,才能顯示。這樣的話,給使用者的影響是一個延遲的現象。

為了解決該問題,我們需要再次從上層往下層瞭解Android繪製圖形的各個細節,並進行優化。對於應用程式開發人員來說,重點還是上層的優化,對自己的應用程式的記憶體,UI,資料等進行優化。

總結:

  • Android通過Buffer來儲存圖形資訊,為了讓圖形顯示的更加流暢,在提供一一個Buffer用於顯示的同時,開闢一個或者多個Buffer用於後臺圖形的合成。
  • Android4.1之前,VSync訊號並未傳遞給上層,導致生產與消費節奏不統一
  • Android4.1之後,上層開始繪製時機都放到了VSync訊號的到來時候
  • 除了在上層引入VSync機制,Anroid在4.1還加入了三緩衝,用來減少卡頓的產生
  • 每個Surface都有自己的繪製流程,需要先經過CPU處理,再經過GPU處理,之後經過SurfaceFlinger與其他Surface繪製好的圖形和合成在一起,供螢幕顯示
  • VSync訊號貫穿整個繪製流程,控制著整個Android圖形系統的節奏

以上內容可能有不對的地方,希望各路大神指教,下一篇文章將從上層往下層講解Android的圖形顯示流程,並把上層的圖形繪製流程展開講解,敬請期待!

相關文章