[TOC]
# 背景知識
### CPU 與 GPU(軟件繪制和硬件繪制)
除了屏幕,UI 渲染還依賴兩個核心的硬件:CPU 與 GPU。UI 組件在繪制到屏幕之前,都需要經過 Rasterization(柵格化)操作,而柵格化操作又是一個非常耗時的操作。GPU(Graphic Processing Unit )也就是圖形處理器,它主要用于處理圖形運算,可以幫助我們加快柵格化操作。

你可以從圖上看到,軟件繪制使用的是 Skia 庫,它是一款能在低端設備如手機上呈現高質量的 2D 跨平臺圖形框架,類似 Chrome、Flutter 內部使用的都是 Skia 庫。
### OpenGL
對于硬件繪制,我們通過調用 OpenGL ES 接口利用 GPU 完成繪制。[OpenGL](https://developer.android.com/guide/topics/graphics/opengl)是一個跨平臺的圖形 API,它為 2D/3D 圖形處理硬件指定了標準軟件接口。而 OpenGL ES 是 OpenGL 的子集,專為嵌入式設備設計。
在官方[硬件加速](https://developer.android.com/guide/topics/graphics/hardware-accel)的文檔中,可以看到很多 API 都有相應的 Android API level 限制。

這是為什么呢?其實這主要是受[OpenGL ES](https://www.khronos.org/opengles/)版本與系統支持的限制,直到最新的 Android P,有 3 個 API 是仍然沒有支持。對于不支持的 API,我們需要使用軟件繪制模式,渲染的性能將會大大降低。

Android 7.0 把 OpenGL ES 升級到最新的 3.2 版本同時,還添加了對[Vulkan](https://source.android.com/devices/graphics/arch-vulkan)的支持。
### Vulkan
Vulkan 是用于高性能 3D 圖形的低開銷、跨平臺 API。相比 OpenGL ES,Vulkan 在改善功耗、多核優化提升繪圖調用上有著非常明顯的[優勢](https://zhuanlan.zhihu.com/p/20712354)。
## Android 渲染的演進
### 圖形系統的

我曾經在一篇文章看過一個生動的比喻,如果把應用程序圖形渲染過程當作一次繪畫過程,那么繪畫過程中 Android 的各個圖形組件的作用是:
* 畫筆:Skia 或者 OpenGL。我們可以用 Skia 畫筆繪制 2D 圖形,也可以用 OpenGL 來繪制 2D/3D 圖形。正如前面所說,前者使用 CPU 繪制,后者使用 GPU 繪制。
* 畫紙:Surface。所有的元素都在 Surface 這張畫紙上進行繪制和渲染。在 Android 中,Window 是 View 的容器,每個窗口都會關聯一個 Surface。而 WindowManager 則負責管理這些窗口,并且把它們的數據傳遞給 SurfaceFlinger。
* 畫板:Graphic Buffer。Graphic Buffer 緩沖用于應用程序圖形的繪制,在 Android 4.1 之前使用的是雙緩沖機制;在 Android 4.1 之后,使用的是三緩沖機制。
* 顯示:SurfaceFlinger。它將 WindowManager 提供的所有 Surface,通過硬件合成器 Hardware Composer 合成并輸出到顯示屏。
接下來我將通過 Android 渲染演進分析的方法,幫你進一步加深對 Android 渲染的理解。
### 1\. Android 4.0:開啟硬件加速[?](https://blog.yorek.xyz/android/paid/master/ui_1/#1-android-40 "Permanent link")
在 Android 3.0 之前,或者沒有啟用硬件加速時,系統都會使用軟件方式來渲染 UI。

整個流程如上圖所示:
* Surface。每個 View 都由某一個窗口管理,而每一個窗口都關聯有一個 Surface。
* Canvas。通過 Surface 的 lock 函數獲得一個 Canvas,Canvas 可以簡單理解為 Skia 底層接口的封裝。
* Graphic Buffer。SurfaceFlinger 會幫我們托管一個[BufferQueue](https://source.android.com/devices/graphics/arch-bq-gralloc),我們從 BufferQueue 中拿到 Graphic Buffer,然后通過 Canvas 以及 Skia 將繪制內容柵格化到上面。
* SurfaceFlinger。通過 Swap Buffer 把 Front Graphic Buffer 的內容交給 SurfaceFinger,最后硬件合成器 Hardware Composer 合成并輸出到顯示屏。
整個渲染流程是不是非常簡單?但是正如我前面所說,CPU 對于圖形處理并不是那么高效,這個過程完全沒有利用到 GPU 的高性能。
#### 硬件加速繪制[?](https://blog.yorek.xyz/android/paid/master/ui_1/#_1 "Permanent link")
所以從 Androd 3.0 開始,Android 開始支持硬件加速,到 Android 4.0 時,默認開啟硬件加速。

硬件加速繪制與軟件繪制整個流程差異非常大,最核心就是我們通過 GPU 完成 Graphic Buffer 的內容繪制。此外硬件繪制還引入了一個 DisplayList 的概念,每個 View 內部都有一個 DisplayList,當某個 View 需要重繪時,將它標記為 Dirty。
當需要重繪時,僅僅只需要重繪一個 View 的 DisplayList,而不是像軟件繪制那樣需要向上遞歸。這樣可以大大減少繪圖的操作數量,因而提高了渲染效率。

### 2\. Android 4.1:Project Butter[?](https://blog.yorek.xyz/android/paid/master/ui_1/#2-android-41project-butter "Permanent link")
優化是無止境的,Google 在 2012 年的 I/O 大會上宣布了 Project Butter 黃油計劃,并且在 Android 4.1 中正式開啟了這個機制。
Project Butter 主要包含兩個組成部分,一個是 VSYNC,一個是 Triple Buffering。
#### VSYNC 信號[?](https://blog.yorek.xyz/android/paid/master/ui_1/#vsync "Permanent link")
在講文件 I/O 跟網絡 I/O 的時候,我講到過中斷的概念。對于 Android 4.0,CPU 可能會因為在忙別的事情,導致沒來得及處理 UI 繪制。
為解決這個問題,Project Buffer 引入了[VSYNC](https://source.android.com/devices/graphics/implement-vsync),它類似于時鐘中斷。每收到 VSYNC 中斷,CPU 會立即準備 Buffer 數據,由于大部分顯示設備刷新頻率都是 60Hz(一秒刷新 60 次),也就是說一幀數據的準備工作都要在 16ms 內完成。

這樣應用總是在 VSYNC 邊界上開始繪制,而 SurfaceFlinger 總是 VSYNC 邊界上進行合成。這樣可以消除卡頓,并提升圖形的視覺表現。
#### 三緩沖機制 Triple Buffering[?](https://blog.yorek.xyz/android/paid/master/ui_1/#triple-buffering "Permanent link")
在 Android 4.1 之前,Android 使用雙緩沖機制。怎么理解呢?一般來說,不同的 View 或者 Activity 它們都會共用一個 Window,也就是共用同一個 Surface。
而每個 Surface 都會有一個 BufferQueue 緩存隊列,但是這個隊列會由 SurfaceFlinger 管理,通過匿名共享內存機制與 App 應用層交互。

整個流程如下:
* 每個 Surface 對應的 BufferQueue 內部都有兩個 Graphic Buffer ,一個用于繪制一個用于顯示。我們會把內容先繪制到離屏緩沖區(OffScreen Buffer),在需要顯示時,才把離屏緩沖區的內容通過 Swap Buffer 復制到 Front Graphic Buffer 中。
* 這樣 SurfaceFlinge 就拿到了某個 Surface 最終要顯示的內容,但是同一時間我們可能會有多個 Surface。這里面可能是不同應用的 Surface,也可能是同一個應用里面類似 SurefaceView 和 TextureView,它們都會有自己單獨的 Surface。
* 這個時候 SurfaceFlinger 把所有 Surface 要顯示的內容統一交給 Hareware Composer,它會根據位置、Z-Order 順序等信息合成為最終屏幕需要顯示的內容,而這個內容會交給系統的幀緩沖區 Frame Buffer 來顯示(Frame Buffer 是非常底層的,可以理解為屏幕顯示的抽象)。
如果你理解了雙緩沖機制的原理,那就非常容易理解什么是三緩沖區了。如果只有兩個 Graphic Buffer 緩存區 A 和 B,如果 CPU/GPU 繪制過程較長,超過了一個 VSYNC 信號周期,因為緩沖區 B 中的數據還沒有準備完成,所以只能繼續展示 A 緩沖區的內容,這樣緩沖區 A 和 B 都分別被顯示設備和 GPU 占用,CPU 無法準備下一幀的數據。

如果再提供一個緩沖區,CPU、GPU 和顯示設備都能使用各自的緩沖區工作,互不影響。簡單來說,三緩沖機制就是在雙緩沖機制基礎上增加了一個 Graphic Buffer 緩沖區,這樣可以最大限度的利用空閑時間,帶來的壞處是多使用的了一個 Graphic Buffer 所占用的內存。

對于 VSYNC 信號和 Triple Buffering 更詳細的介紹,可以參考[《Android Project Butter 分析》](https://blog.csdn.net/innost/article/details/8272867)。
#### 數據測量[?](https://blog.yorek.xyz/android/paid/master/ui_1/#_2 "Permanent link")
“工欲善其事,必先利其器”,Project Butter 在優化 UI 渲染性能的同時,也希望可以幫助我們更好地排查 UI 相關的問題。
在 Android 4.1,新增了 Systrace 性能數據采樣和分析工具。在卡頓和啟動優化中,我們已經使用過 Systrace 很多次了,也可以用它來檢測每一幀的渲染情況。
Tracer for OpenGL ES 也是 Android 4.1 新增加的工具,它可逐幀、逐函數的記錄 App 用 OpenGL ES 的繪制過程。它提供了每個 OpenGL 函數調用的消耗時間,所以很多時候用來做性能分析。但因為其強大的記錄功能,在分析渲染問題時,當 Traceview、Systrace 都顯得棘手時,還找不到渲染問題所在時,此時這個工具就會派上用場了。
在 Android 4.2,系統增加了檢測繪制過度工具,具體的使用方法可以參考[《檢查 GPU 渲染速度和繪制過度》](https://developer.android.com/studio/profile/inspect-gpu-rendering)。

### 3\. Android 5.0:RenderThread[?](https://blog.yorek.xyz/android/paid/master/ui_1/#3-android-50renderthread "Permanent link")
經過 Project Butter 黃油計劃之后,Android 的渲染性能有了很大的改善。但是不知道你有沒有注意到一個問題,雖然我們利用了 GPU 的圖形高性能運算,但是從計算 DisplayList,到通過 GPU 繪制到 Frame Buffer,整個計算和繪制都在 UI 主線程中完成。

UI 主線程“既當爹又當媽”,任務過于繁重。如果整個渲染過程比較耗時,可能造成無法響應用戶的操作,進而出現卡頓。GPU 對圖形的繪制渲染能力更勝一籌,如果使用 GPU 并在不同線程繪制渲染圖形,那么整個流程會更加順暢。
正因如此,在 Android 5.0 引入了兩個比較大的改變。一個是引入了 RenderNode 的概念,它對 DisplayList 及一些 View 顯示屬性做了進一步封裝。另一個是引入了 RenderThread,所有的 GL 命令執行都放到這個線程上,渲染線程在 RenderNode 中存有渲染幀的所有信息,可以做一些屬性動畫,這樣即便主線程有耗時操作的時候也可以保證動畫流暢。
在官方文檔[《檢查 GPU 渲染速度和繪制過度》](https://developer.android.com/studio/profile/inspect-gpu-rendering)中,我們還可以開啟 Profile GPU Rendering 檢查。在 Android 6.0 之后,會輸出下面的計算和繪制每個階段的耗時:

如果我們把上面的步驟轉化線程模型,可以得到下面的流水線模型。CPU 將數據同步(sync)給 GPU 之后,一般不會阻塞等待 GPU 渲染完畢,而是通知結束后就返回。而 RenderThread 承擔了比較多的繪制工作,分擔了主線程很多壓力,提高了 UI 線程的響應速度。

# 參考資料
[# 20 | UI 優化(上):UI 渲染的幾個關鍵概念](https://blog.yorek.xyz/android/paid/master/ui_1/)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺