gpu 渲染 surface與canvas

Android應用程序UI硬件加速渲染技術簡要介紹和學習計劃

羅昇陽

       Android系統的流暢性一直被拿來與iOS比較,並且認爲不如後者。這一方面與Android設備硬件質量參差不齊有關,另一方面也與Android系統的實現有關。例如在3.0前,Android應用程序UI繪製不支持硬件加速。不過從4.0開始,Android系統一直以“run fast, smooth, and responsively”爲目標對UI進行優化。本文對這些優化進行簡要介紹和制定學習計劃。

老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!

《Android系統源代碼情景分析》一書正在進擊的程序員網(http://0xcc0xcd.com)中連載,點擊進入!

       注意,上面我們說Android系統不支持硬件加速的UI 繪製,針對的是Android應用程序2D UI繪製。對於3D UI,例如遊戲,一直是支持硬件加速渲染的。此外,從前面Android應用程序與SurfaceFlinger服務的關係概述和學習計劃、Android系統Surface機制的SurfaceFlinger服務簡要介紹和學習計劃和Android應用程序窗口(Activity)實現框架簡要介紹和學習計劃這三個系列的文章可以知道,Android系統的UI從繪製到顯示到屏幕是分兩步進行的:第一步是在Android應用程序進程這一側進行的;第二步是在SurfaceFlinger進程這一側進行的。前一步將UI繪製一個圖形緩衝區中,並且將該圖形緩衝區交給後一步進行合成以及顯示在屏幕中。其中,後一步的UI合成一直都是以硬件加速方式完成的。

      在支持Android應用程序UI硬件加速渲染之前,Android應用程序UI的繪製是以軟件方式進行的,爲了更好地理解Android應用程序UI硬件加速渲染技術,我們先回顧在Android應用程序窗口(Activity)實現框架簡要介紹和學習計劃這個系列的文章提及的軟件渲染技術,如圖1所示:

 

圖1 Android應用程序UI軟件渲染過程

      在Android應用程序進程這一側,每一個窗口都關聯有一個Surface。每當窗口需要繪製UI時,就會調用其關聯的Surface的成員函數lock獲得一個Canvas,其本質上是向SurfaceFlinger服務Dequeue一個Graphic Buffer。Canvas封裝了由Skia提供的2D UI繪製接口,並且都是在前面獲得的Graphic Buffer上面進行繪製的。繪製完成之後,Android應用程序進程再調用前面獲得的Canvas的成員函數unlockAndPost請求顯示在屏幕中,其本質上是向SurfaceFlinger服務Queue一個Graphic Buffer,以便SurfaceFlinger服務可以對Graphic Buffer的內容進行合成,以及顯示到屏幕上去。

      接下來我們再來看Android應用程序UI硬件加速渲染技術,如圖2所示:

 

圖2 Android應用程序UI硬件加速渲染過程

       這裏我們首先要明確什麼是硬件加速渲染,其實就是通過GPU來進行渲染。GPU作爲一個硬件,用戶空間是不可以直接使用的,它是由GPU廠商按照Open GL規範實現的驅動間接進行使用的。也就是說,如果一個設備支持GPU硬件加速渲染,那麼當Android應用程序調用Open GL接口來繪製UI時,Android應用程序的UI就是通過硬件加速技術進行渲染的。因此,在接下來的描述中,我們提及到GPU、硬件加速和Open GL時,它們表達的意思都是等價的。

       從圖2可以看到,硬件加速渲染和軟件渲染一樣,在開始渲染之前,都是要先向SurfaceFlinger服務Dequeue一個Graphic Buffer。不過對硬件加速渲染來說,這個Graphic Buffer會被封裝成一個ANativeWindow,並且傳遞給Open GL進行硬件加速渲染環境初始化。在Android系統中,ANativeWindow和Surface可以是認爲等價的,只不過是ANativeWindow常用於Native層中,而Surface常用於Java層中。另外,我們還可以將ANativeWindow和Surface看作是像Skia和Open GL這樣圖形渲染庫與操作系統底層的圖形系統建立連接的一個橋樑。

       Open GL獲得了一個ANativeWindow,並且進行了硬件加速渲染環境初始化工作之後,Android應用程序就可以調用Open GL提供的API進行UI繪製了,繪製出來內容就保存在前面獲得的Graphic Buffer中。當繪製完畢,Android應用程序再調用libegl庫提供的一個eglSwapBuffer接口請求將繪製好的UI顯示到屏幕中,其本質上與軟件渲染過程是一樣的,都是向SurfaceFlinger服務Queue一個Graphic Buffer,以便SurfaceFlinger服務可以對Graphic Buffer的內容進行合成,以及顯示到屏幕上去。

       關於Android應用程序UI的硬件加速渲染過程中涉及到Open GL環境初始化和繪製的簡化版本,可以參考前面Android系統的開機畫面顯示過程分析一文提到的Android系統開機動畫的實現。在Android系統的開機畫面顯示過程分析這篇文章中,開機動畫其實是由一個/system/bin/bootanimation程序實現的。這個程序可以看成是一個沒有使用Android SDK來開發的一個Native應用程序。

       在這個系列的文章中,我們將通過Android 5.0的源碼來分析Android應用程序UI的硬件加速渲染技術。不過爲了更好地理解Android 5.0的硬件加速渲染實現,我們有必要先了解從Android 3.0以來,Android應用程序UI硬件加速渲染的進化歷史:

       1. Android 3.0,也就是Honeycomb版本,開始引用OpenGLRenderer圖形渲染庫,支持Android應用程序UI可選地使用硬件加速渲染。

       2. Android 4.0,也就是Ice Cream Sandwich版本,要求設備默認支持Android應用程序UI硬件加速渲染,並且增加一個TextureView控件,該控件直接支持以Open GL紋理的形式來繪製UI。

       3. Android 4.1、4.2和4.3,也就是Jelly Bean版本,加入了Project Butter(黃油計劃)的特性,包括:A. 通過Vsync信號來同步UI繪製和動畫,使得它們可以獲得一個達到60fps的固定的幀率;B. 三緩衝支持,改善GPU和CPU之間繪製節奏不一致的問題;C. 將用戶輸入,例如touch event,同步到下一個Vsync信號到來時再處理;D. 預測用戶的touch行爲,以獲得更好的交互響應;E. 每次用戶touch屏幕時,進行CPU Input Boost,以便減少處理延時。

       4. Android 4.4,也就是KitKat版本,一方面通過優化內存使用,另一方面是可選地支持使用ART運行時替換Dalvik虛擬機,來提高應用程序的運行效率,使得其UI更流暢。

       5. Android 5.0,也就是Lollipop版本,ART運行時引進了Compacting GC,進一步優化了Android應用程序的內存使用,並且ART運行時正式替換了Dalvik虛擬機,同時,Android應用程序增加了一個Render Thread,專門負責UI渲染和動畫顯示。

       從Android應用程序UI硬件加速渲染的進化歷史可以看出,Android系統確實是在踐行"run fast, smooth, and responsively"的宏偉計劃,並且也是做到了。

       有了前面的基礎知識之後,我們接下來再來Android 5.0的窗口和動畫是如何通過硬件加速技術來渲染的,如圖3所示:

 

圖3 Android應用程序窗口和動畫的硬件加速渲染框架

       在Android應用程序窗口中,每一個View都抽象爲一個Render Node,而且如果一個View設置有Background,這個Background也被抽象爲一個Render Node。這是由於在OpenGLRenderer庫中,並沒有View的概念,所有的一切可繪製的元素都抽象爲一個Render Node。

       每一個Render Node都關聯有一個Display List Renderer。這裏又涉及到另外一個概念——Display List。注意,這個Display List不是Open GL裏面的Display List,不過它們在概念上是差不多的。Display List是一個繪製命令緩衝區。也就是說,當View的成員函數onDraw被調用時,我們調用通過參數傳遞進來的Canvas的drawXXX成員函數繪製圖形時,我們實際上只是將對應的繪製命令以及參數保存在一個Display List中。接下來再通過Display List Renderer執行這個Display List的命令,這個過程稱爲Display List Replay。

       引進Display List的概念有什麼好處呢?主要是兩個好處。第一個好處是在下一幀繪製中,如果一個View的內容不需要更新,那麼就不用重建它的Display List,也就是不需要調用它的onDraw成員函數。第二個好處是在下一幀中,如果一個View僅僅是一些簡單的屬性發生變化,例如位置和Alpha值發生變化,那麼也無需要重建它的Display List,只需要在上一次建立的Display List中修改一下對應的屬性就可以了,這也意味着不需要調用它的onDraw成員函數。這兩個好處使用在繪製應用程序窗口的一幀時,省去很多應用程序代碼的執行,也就是大大地節省了CPU的執行時間。

       注意,只有使用硬件加速渲染的View,纔會關聯有Render Node,也就纔會使用到Display List。我們知道,目前並不是所有的2D UI繪製命令都是GPU可以支持的。這一點具體可以參考官方說明文檔:http://developer.android.com/guide/topics/graphics/hardware-accel.html。對於使用了GPU不支持的2D UI繪製命令的View,只能通過軟件方式來渲染。具體的做法是將創建一個新的Canvas,這個Canvas的底層是一個Bitmap,也就是說,繪製都發生在這個Bitmap上。繪製完成之後,這個Bitmap再被記錄在其Parent View的Display List中。而當Parent View的Display List的命令被執行時,記錄在裏面的Bitmap再通過Open GL命令來繪製。

       另一方面,對於前面提到的在Android 4.0引進的TextureView,它也不是通過Display List來繪製。由於它的底層實現直接就是一個Open GL紋理,因此就可以跳過Display List這一中間層,從而提高效率。這個Open GL紋理的繪製通過一個Layer Renderer來封裝。Layer Renderer和Display List Renderer可以看作是同一級別的概念,它們都是通過Open GL命令來繪製UI元素的。只不過前者操作的是Open GL紋理,而後者操作的是Display List。

       我們知道,Android應用程序窗口的View是通過樹形結構來組織的。這些View不管是通過硬件加速渲染還是軟件渲染,或者是一個特殊的TextureView,在它們的成員函數onDraw被調用期間,它們都是將自己的UI繪製在Parent View的Display List中。其中,最頂層的Parent View是一個Root View,它關聯的Root Node稱爲Root Render Node。也就是說,最終Root Render Node的Display List將會包含有一個窗口的所有繪製命令。在繪製窗口的下一幀時,Root Render Node的Display List都會通過一個Open GL Renderer真正地通過Open GL命令繪製在一個Graphic Buffer中。最後這個Graphic Buffer被交給SurfaceFlinger服務進行合成和顯示。

       以上就是Android應用程序窗口和動畫的硬件加速渲染框架,裏面提到的Render Thread還需要進一步解釋。Render Thread是在Android 5.0中引進的,它用來分擔Android應用程序的Main Thread的工作。在Android 5.0之前,Android應用程序的Main Thread不僅負責渲染UI,還負責處理用戶輸入。通過引進Render Thread,我們就可以將UI渲染工作從Main Thread釋放出來,交由Render Thread來處理,從而也使得Main Thread可以更專注高效地處理用戶輸入,這樣使得在提高UI繪製效率的同時,也使得UI具有更高的響應性。

       Main Thread與Render Thread的交互模型如圖4所示:

 

圖4 Android應用程序Main Thread與Render Thread的交互模型

       Main Thread主要是負責調用View的成員函數onDraw來構造它們的Display List,然後在下一個Vsync信號到來時,再通過一個Render Proxy對象向Render Thread發出一個drawFrame命令。Render Thread內部有一個Task Queue,從Main Thread發送過來的drawFrame命令就會保存在Render Thread的Task Queue,等待Render Thread處理。

       上面分析的應用程序UI繪製機制還沒有涉及到動畫。當一個View需要以動畫的形式顯示時,我們可以通過調用這個View的成員函數animate獲得一個ViewPropertyAnimator。ViewPropertyAnimator可以通過兩種方式來顯示動畫。第一種方式是舊的方式,動畫的每一幀由Main Thread進行計算,然後再由Render Thread進行渲染。第二種方式是Android 5.0引進的,Main Thread將動畫註冊到Render Thread中去,然後由Render Thread計算和顯示動畫的每一幀。第二種方式在動畫的顯示期間,完全不需要Main Thread參與,不過前提是Main Thread不想參與。如果Main Thread需要參與,例如,Main Thread想在動畫開始之前,將View的Layer Type設置爲LAYER_TYPE_HARDWARE,以便它可以以Open GL的Frame Buffer Object(FBO)的形式進行渲染,或者Main Thread想偵聽動畫的每一幀顯示,那麼就不能將動畫的計算和顯示完全交給Render Thread來做了。將動畫的計算和顯示完全交給Render Thread來做的好處就是使得動畫的顯示不影響Main Thread響應用戶的其它輸入。


       上面提到,在顯示一個View的動畫之前,Main Thread可以將View的Layer Type設置爲LAYER_TYPE_HARDWARE,這樣View可以以FBO的形式進行渲染更,這是通過調用與View關聯的ViewPropertyAnimator的成員函數withLayer來實現的。這是對動畫顯示的另一種優化。回憶TextureView的特點,它是直接通過Open GL紋理來繪製,這樣可以省去Display List這一中間步驟。同樣的,對於Layer Type爲LAYER_TYPE_HARDWARE的View,它將直接通過FBO來實現,這樣也是可以提高渲染效率。等到動畫結束的時候,ViewPropertyAnimator會將View的Layer Type將恢復爲原來設置的類型。

       上面描述的兩種動畫顯示優化涉及到Main Thread與Render Thread的交互 如圖5所示:


圖5 Android應用程序Main Thread與Render Thread的動畫交互模型

        從View獲得一個用來顯示動畫的ViewPropertyAnimator之後,調用它的成員函數withLayer,就會導致Main Thread向Render Thread發送一個buildLayer的命令。Render Thread執行這個buildLayer的命令的時候,就會爲與View關聯的Render Node設置一個Layer。以後Render Thread就以Layer的方式,即FBO的方式,來渲染View的動畫。      

        另一方面,如果一個View的動畫顯示不需要Main Thread的參與,那麼從View獲得一個ViewPropertyAnimator會將動畫註冊到Render Thread裏面的一個AnimatorManager中。Render Thread通過AnitmatorManager檢測註冊到它裏面的動畫是否還沒有結束。如果還沒有結束,那麼Render Thread就自動地計算和顯示動畫的下一幀,直到動畫顯示結束爲止。

       至此,Android應用程序UI的硬件加速渲染涉及到的關鍵概念我們就介紹完成了,接下來我們還會按照以下五個情景進一步分析它的實現:

       1. Android應用程序UI硬件加速渲染的環境初始化過程分析;

       2. Android應用程序UI硬件加速渲染的預加載資源地圖集創建過程分析;

       3. Android應用程序UI硬件加速渲染的Display List構建過程分析;

       4. Android應用程序UI硬件加速渲染的Display List渲染過程分析;

       5. Android應用程序UI硬件加速渲染的動畫執行過程分析。

       其中,第二個情景是第四個情景涉及到的一個重要優化,因此我們將它單獨列出來分析。通過這五個情景的學習,我們就可以深入地掌握Android應用程序UI的硬件加速渲染技術了,敬請期待!更多的信息也可以關注老羅的新浪微博:http://weibo.com/shengyangluo。
————————————————
版權聲明:本文爲CSDN博主「羅昇陽」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/luoshengyang/article/details/45601143

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章