Android 重學系列 SurfaceFlinger的概述

前言

本文將會作為開啟SurfaceFlinger的系列第一篇文章。然而SurfaceFlinger幾乎貫通了整個Android領域中所有的知識。從HAL硬件抽象層到Framework層,從CPU繪制到OpenGL等硬件繪制。

為了讓整個系列的書寫更有邏輯性。這一次我將一反常態,先把整個架構的設計思想概述寫出來,作為后面的系列文章的指導。本文之后都會將SurfaceFlinger稱為SF。

遇到什么疑問,可以來本文下討論:http://www.lxweimin.com/p/c954bcceb22a

正文

SF的渲染第一定律:

SF是整個Android系統渲染的核心進程。所有應用的渲染邏輯最終都會來到SF中進行處理,最終會把處理后的圖像數據交給CPU或者GPU進行繪制。

姑且讓我們先把這句話當作Android渲染系統的第一定律。SF在整個Android系統中,并非擔當渲染的角色,而是作為圖元拋射機一樣,把所有應用進程傳遞過來的圖元數據加工處理后,交給CPU和GPU做真正的繪制。

SF的渲染第二定律:

在每一個應用中都以Surface作為一個圖元傳遞單元,向SF這個服務端傳遞圖元數據。

這是Android渲染體系的第二定律。把這兩個規律組合起來就是如下一個簡單示意圖。

SF交互設計圖.png

SF的渲染第三定律:

SF是以生產者以及消費者為核心設計思想,把每一個應用進程作為生產者生產圖元保存到SF的圖元隊列中,SF則作為消費者依照一定的規則把生產者存放到SF中的隊列一一處理。

用圖表示就如下:


圖元消費核心原理.png

SF體系渲染的第四定律:

為了能夠跨進程的傳輸大容量的圖元數據,使用了匿名共享內存內存作為工具把圖元數據都輸送到SF中處理。

眾所周知,我們需要從應用進程跨進程把圖元數據傳輸到SF進程中處理,就需要跨進程通信,能考慮的如socket這些由于本身效率以及數據拷貝了2份(從物理內存頁層面上來看),確實不是很好的選擇。一個本身拷貝大量的數據就是一個疑問。那么就需要那些一次拷貝的進程間通信方式,首先能想到的當然是Binder,然而Binder進程間通信,特別是應用的通信數據總量只有1M不到的大小加上應用其他通信,勢必會出現不足的問題。

為了解決這個問題,Android使用共享內存,使用的是匿名共享內存(Ashmem)。匿名共享內存也是一種拷貝一次的進程間通信方式,其核心比起binder的復雜的mmap更加接近Linux的共享內存的概念。

Ashmem.png

SF體系渲染的第五定律:

SF底層有一個時間鐘在不斷的循環,或從硬件中斷發出,或從軟件模擬發出計時喚起,每隔一段時間都會獲取SF中的圖元隊列通過CPU/GPU繪制在屏幕。

第五定律的誕生實際上很符合Android系統的設計情況,除了需要Android應用有辦法通知SF需要渲染的模式,當然需要SF自己不斷的把圖元繪制到屏幕的行為的自己回調自己的行為,SF自己不斷的繪制在SF中的圖元數據。

SF Vsync.png

其中EventThread扮演一個極其重要的角色,在SF中設計大致如下:


EventThread.png

Vsync的介紹

這里面出現了一個新的名次VSync,其實這就是我們玩游戲經常說的垂直同步信號。我以前用渣電腦玩游戲的時候,經常掉幀數卡的不行,之后關閉了垂直信號后感覺好了點,讓我有一段時間以為這是個不好的東西。

這里就先介紹一下Android曾經迭代為ui體驗更好上的努力,黃油計劃。黃油計劃故名思議就是為了讓系統的ui表現如黃油表面一樣順滑。為此誕生了兩個重要的概念Vsync以及Triple Buffer,即垂直信號和三重緩沖。

雙緩沖的概念大家應該都熟悉,在OpenGL我已經解釋過了,雙緩沖就是渲染第一幀的同時已經在繪制第二幀的內容,等到第二幀繪制完畢后就顯示出來。這么做的好處很明顯,如果一幀畫完,才開始畫下一幀,勢必有一個計算的過程導致ui交互遲緩。

雙緩沖.png

通過這種方式顯示前一幀的時候提前繪制好下一幀圖元,放在背后等待時機交換,這樣就能從感官上流暢不少。

這么做理想十分顯示,但是怎么找到一個合適的時機進行交換前后兩幀這是一個問題?如果有人在想那就按照屏幕刷新頻率來,一般按照通用屏幕刷新60fps也就是約16ms刷新一次即可。

理想是很豐滿,但是現實很骨干,這么做好像沒有問題,我們深入考慮一下,其實這個過程中有兩個變量,一個是繪圖速度,一個是顯示速度。就算是繪圖速度中也有分CPU和GPU的繪制速度。

這里就沿用一下當年google在宣傳黃油計劃時候的示意圖。讓我們先看看沒有緩沖正常運作的示意圖:


draw_vsync.png

最好的情況就是上圖,在顯示第0幀的時候,CPU/GPU合成繪制完成第1幀在16ms內,當vsync信號來了,就把第1幀交換到顯示屏顯示。

vsync是什么?玩游戲的時候經常看到垂直同步就是它。它的作用是通過屏幕硬件中斷來告訴系統應該什么時候刷新屏幕。通過這樣的方式,大致上16ms的發送一次中斷讓系統刷新。

但是很可能出現下面這種情況,CPU因為繁忙來不及,顯示完第一幀的時候,還沒空渲染第二幀,就算SF接受到了Vsync的信號,也只能拿出已經渲染好的第一幀顯示在屏幕上。這樣就重復顯示了第一幀,Google開發團隊稱這種為jank。


jank.png

能看到顯示第一幀因為第二幀沒準備好,只能重復顯示第一幀了。

再來看看帶著多重緩沖的的工作原理流程:


double_buffer.png

能看到此時就不是簡單的第一第二幀,而是分為A緩沖,B緩沖。能看到在正常情況下,先顯示A緩沖的內容,同時準備B緩沖,當一切正常的時候,B緩沖應該在下一個vsync來之前準備好,一旦vsync到來則顯示B緩沖,A緩沖回到后臺繼續繪制。

那么這種方式一旦遇到jank會是怎么一個情況呢?


double_jank.png

如果是雙緩沖好像沒有問題,但是一旦出現jank了之后,之后顯示屏就會不斷的出現jank。如果緩沖A在顯示,而B準備的時間超過16ms,就會導致A緩沖區重復顯示,而B當b顯示的時候,A也很可能準備時間不足16ms導致無法繪制完成,只能重復顯示B緩沖的內容。

這種方式更加的危險,為了解決這個問題,Google引入三重緩沖。

當三重緩沖處理jank的原理流程圖:


triple_buffer.png

能看到為了避免后面連鎖式的錯誤,引入三重緩沖就為了讓空閑出來的等待時間,能夠做更多的事情。就如同雙緩沖遇到jank之后,一旦B緩沖CPU+GPU的時間超過了下一個vsync的時間,能夠發現其實CPU和GPU有一段時間都沒有事情做,光等待下一次Vsync的到來,才會導致整個系統后面的繪制出現連鎖式的出現jank。

而三緩沖的出現,在重復顯示A緩沖區的時候,CPU不會光等待而是會準備C緩沖區的圖元,之后就能把C緩沖區接上。這就是Google所說的三重緩沖區的來源。

不過絕大多數情況都緩沖策略是由SF系統自己決定的,一般我們常說的雙緩沖,三緩沖指的就是這個。

實際上這種方式也可以用到音視頻的編寫優化,里面常用的緩沖區設計和這里也有同工異曲的之妙,但是沒有系統如此極致。如果閱讀過系統的videoView源碼就能看到NullPlayer本質上就是借助Surface圖元緩沖區來達到極致的體驗,不過VideoView也有設計不合理的地方,之后研讀完Android的渲染體系,讓我們來分析分析這些源碼。

但是這一部分的知識,不足以讓我們去理解定律5.其實每一次Vsync從硬件/軟件過來的時候,Dispsync都會嘗試著通知SF和app,這是完全沒有問題,但是后面那個Phase相位又是什么東西?

其實這就是系統的設計的巧妙,我們如果同時把信號通知同時告訴app和sf會導致什么結果?


無phase的沖突.png

如果此時app后返回了圖元,但是sf已經執行了刷新合成繪制行為(很有可能,因為app到sf傳輸圖元速度必定比sf自己通知自己慢),此時就會導致類似jank的問題,導致下一個vsync還是顯示當前幀數,因此需要如下一個時間差,先通知app后通知sf,如下圖:


sf和app的時間差.png

加上這個理解就能明白第五定律。關于第五點的討論,在Vsync同步信號原理有詳細討論。

小結

這五大定律是指導SF設計的核心思想,從Android4.1一直到9.0都沒有太大的變化。只要抓住這五個核心思想,我們閱讀起SF的難度就會下降不少。

那么SF的體系和我之前聊過的Skia有什么關系呢?又和頂層的View的繪制流程有什么關系呢?

我們按照角色區分一下:

  1. framework面向開發者所有的View是便于開發的控件,里面僅僅只是提供了當前View各種屬性以及功能。
  2. 而Android底層的Skia是Android對于屏幕上的畫筆,經過View繪制流程的onDraw方法回調,把需要繪制的東西通過Skia繪制成像素圖元保存起來
  3. SF則是最后接受Skia的繪制結果,最后繪制到屏幕上。

所以說,Skia是Android渲染核心這句話沒錯,但是最終還是需要Skia和系統所提供起來,才是一個Android完整渲染體系。

經過這一層層的屏蔽,讓開發者不需要對Android底層的渲染體系有任何理解,也能繪制出不錯的效果。

Android的渲染流程.png

最后會把繪制結果傳輸到屏幕中。

因此,本次計劃將會從底層核心,慢慢向上剖析,直到View的繪制流程,讓我們那徹底通讀整個android的渲染體系。

計劃

本次計劃SurfaceFlinger的文章將會通過如下模塊一一解析(但是不代表一個模塊就只有一篇,也不代表最終順序,僅僅代表你將會閱讀到什么內容):

    1. 圖元核心傳輸工具,匿名共享內存ashmem驅動的核心原理,ashmem原理圖大致如下:


      ashmem.png

詳見匿名內存ashmem源碼分析。然而在Android高版本,已經放棄了ashemem,改用ion驅動。ion的原理圖大致如下:

GraphicBuffer和ion.png

關于ion的分析,詳見ion驅動源碼淺析

ion實際上是生成DMA直接訪問內存。原本ashmem的方式需要從GPU訪問到CPU再到內存中的地址。但是在這里就變成了GPU直接訪問修改DMA,CPU也能直接修改DMA。這就是最大的變化。

    1. 開機沒有Activity,只能直接使用SF機制加上OpenGL es顯示開機動畫,來看看從linux開機動畫到Android開機動畫 BootAnimation 。
      詳見系統啟動動畫,原理圖大致如下:
      開機動畫啟動原理.jpg
    1. 理解應用進程如何和SF構建起聯系。
      詳見Vsync同步信號原理。SF是通過一個名為Choreographer監聽VSync進而得知繪制周期的。原理圖大致如下:
      VSync回調機制.jpg
    1. SF硬件抽象層hal的理解和運作,理解SF如何和底層HWC/fb驅動關聯起來。

詳見SurfaceFlinger 的HAL層初始化
其核心數據結構如下:

HWC關鍵數據結構.jpg

底層硬件回調和SF之間的關聯原理圖如下:


ComposerCallback.png
    1. SF是如何連通DisplayManagerService[略,之后有機會進行補充],只是簡單的通過SurfaceFlinger獲取屏幕信息放在Framework層管理。
    1. Android端在opengl es的核心原理,看看Android對opengl es上做了什么封裝。
      這個模塊分為兩部分解析:
      一個是正常的OpenGL es使用流程中,軟件模擬每一個關鍵步驟的工作原理是什么,Android在其中進行了什么優化。詳見OpenGL es上的封裝(上)
      其中有一個十分關鍵的數據結構,UML圖如下:
      紋理結構.png

一個紋理在OpenGL es中是如何合成繪制的,并且Android進行了本地紋理的優化,詳見OpenGL es上的封裝(下),整個OpenGL es的繪制原理如下:

OpenGL es紋理繪制過程.png

    1. 圖元是怎么通過hal層生產出圖元數據;應用的圖元數據又是獲取到應用,如何進入SurfaceFlinger的緩沖隊列。
      詳見GraphicBuffer的誕生。其中涉及了幾個重要的數據結構:
      GraphicBuffer生成體系.png

同時運行原理圖如下:


GraphicBuffer誕生到可使用.png
    1. 應用的圖元數據是如何消費的。
      詳見圖元的消費,交換緩沖繪制參數,本質是取出一個GraphicBuffer存到緩沖隊列的時間和當前時間預計顯示最接近的一個,渲染到屏幕中。同時把上一幀的GraphicBuffer放到空閑隊列中。

其中,我們需要記住下面這個SF中緩沖隊列設計的數據結構:


Layer與緩沖隊列的設計.png
    1. SF是如何通過HWC合成圖層,如何合并各個Layer,輸出到opengles中處理。
      大致上可以分為如下如下7步驟:
      1.preComposition 預處理合成
      2.rebuildLayerStacks 重新構建Layer棧
      3.setUpHWComposer HWC的渲染或者準備
      這三步驟,我稱為繪制準備,詳見圖元的合成(上) 繪制的準備
      在繪制準備的過程中,最重要的是區分了如下幾種繪制模式,已經存儲相關的數據到HWC的Hal層中。
Composition的Layer的Type hasClientComposition hasDeviceComposition 渲染方式
HWC2::Composition::Client true - OpenGL es
HWC2::Composition::Device - true HWC
HWC2::Composition::SolidColor - true HWC
HWC2::Composition::Sideband - true HWC或者OpenGL es

4.doDebugFlashRegions 打開debug繪制模式
5.doTracing 跟蹤打印
6.doComposition 合成圖元
7.postComposition 圖元合成后的vysnc等收尾工作。

后面四個步驟,我們只需要關注最后兩個步驟即可。詳見圖元的合成(下)

整一套的從消費到合成的流程原理圖大致如下:


SF的圖元合成.png

在合成的過程中,分為HWC和OpenGL es兩種,兩者負責的角色大致如下:


SF的圖元合成設計.png

當然,在Android渲染體系中,也不是只有一對生產者消費者模型:


SF所有生產消費者.png
    1. SF的Vsync原理,以及相位差計算原理
      整個VSync發送中有三種發送周期:硬件發送VSync周期,軟件發送VSync周期,app處理VSync周期,sf處理VSync周期。
      詳見Vsync同步信號原理

Android為了方便,會暫時把整個周期看成一個周期連續性的函數,計算原理如下:


計算角度.png

其實就是獲取每一個采樣點相位,計算采樣點相位的平均值就是理想相位。同理,周期也是計算采樣點的平均周期,從而計算出一個合適的軟件發送VSync軸。

最后在軟件渲染的基礎上,app的VSync和sf的VSync各自進行延時接受處理,避免出現定律的時序沖突,就是上面那一副藍色的圖。

    1. SF的fence 同步柵工作原理
      詳見fence原理
      想要弄懂Fence,需要先了解GraphicBuffer的狀態變更:
      大致分為如下幾個狀態:dequeue(出隊到應用中繪制),queue(入隊到SF緩沖區等待消費),acquire(選擇渲染的GraphicBuffer),free(消費完畢后等待dequeue)
      GraphicBuffer狀態流轉.png

Fence的狀態更簡單,有acquire,release,retried狀態流轉大致如下:


fence轉化流程圖.png

retried是每一次繪制完都會合并在一個不用的Fence中進行記錄。

總結一句話,Fence的acquire狀態其實是阻塞什么時候可以被消費,什么時候可以被渲染到屏幕;而Fence的release狀態則是控制什么時候可以出隊給應用進行繪制,什么時候可以被映射到內存。

只有理解這12點,才能說你了解SF了,也不能說精通,畢竟你沒辦法盲敲出來。

等這12點全部理解通之后,會開啟Skia新的篇章,來聊聊Skia的工作原理以及源碼解析,最后我們會回歸本源,來聊聊View的繪制流程以及WMS。

這個只是一個導讀,在這12個知識點背后藏著不少的東西,希望一個總綱能讓人有一個總攬,不至于迷失在源碼中。

后話

作為每一個經常和UI交互的工程師,有必要也必須要熟悉Android 的渲染原理,只有這樣才能讓我們寫出更加優秀代碼,特別是做音視頻的哥們,更加有必要閱讀這些代碼以及看看工業級別的Android的VideoView是如何設計的。其實我在學習一些關于音視頻資料的時候,用ffmpeg編寫一個視頻播放器,發現其實那些demo還有很多地方可以優化的,可以學習flutter如何工作的,如何依托自身平臺做進一步優化,而不是應該去做一個泛用的,還過得去的東西。

我會隨著進度不斷修改本文,本文不是最終版本,會不斷的添加不少設計示意圖以及UML圖。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容