UI渲染基礎(chǔ)
1斥扛、屏幕與適配
通過dp和自適應(yīng)布局可以基本解決屏幕碎片化的問題,這也是Android推薦使用的屏幕兼容性適配方案,但它存在兩個(gè)比較大的問題:
- 不一致行,由于dpi與實(shí)際的ppi的差異性疾党,導(dǎo)致在相同分辨率的手機(jī)上,控件的實(shí)際大小會(huì)有所不同
- 效率陆蟆,設(shè)計(jì)師設(shè)計(jì)稿都是以px為單位的字支,開發(fā)人員為了UI適配凤藏,需要手動(dòng)通過百分百估算出dp值
除了直接dp適配之外奸忽,目前比較常用的適配方案有兩種: - 限制符適配方案,主要有寬高限定符與smallestWidth限定符適配方案
- 今日頭條適配方案揖庄,通過反射修正系統(tǒng)的density值
2栗菜、CPU與GPU
UI組件在繪制到屏幕之前,都需要經(jīng)過Rasterization(柵格化)操作蹄梢,而柵格化操作有是一個(gè)非常耗時(shí)的操作疙筹,GPU也就是圖形處理器,它主要用于處理圖形運(yùn)算禁炒,可以幫助我們加快柵格化操作而咆。
3、OpenGL和Vulkan
硬件繪制幕袱,我們通過調(diào)用OpenGL ES接口利用GPU完成繪制翘盖,OpenGL是一個(gè)跨平臺(tái)的圖形API,它為2D/3D圖形處理硬件制定了標(biāo)準(zhǔn)軟件接口凹蜂,而OpenGL ES是OpenGL的子集馍驯,專為嵌入式設(shè)備設(shè)計(jì)。
Android 7.0把OpenGL ES升級(jí)到最新的3.2版本同時(shí)玛痊,還添加了對(duì)Vulkan的支持汰瘫。Vulkan是用于高性能的3D圖形的低開銷、跨平臺(tái)API擂煞。在改善功耗混弥、多核優(yōu)化提升繪圖調(diào)用上有著非常明顯的優(yōu)勢(shì)。
Android渲染
Android各組件的作用:
- 畫筆:Skia或者OpenGL对省,我們用Skia畫筆繪制2D圖形蝗拿,也可以用OpenGL來繪制2D/3D圖形
- 畫紙:Surface,在Android 中Window是View的容器蒿涎,每個(gè)窗口都會(huì)關(guān)聯(lián)一個(gè)Surface哀托,而WindowManager則負(fù)責(zé)管理這些窗口,并把它們的數(shù)據(jù)傳遞給SurfaceFlinger
- 畫板:Graphic Buffer劳秋,用于應(yīng)用程序圖形的繪制仓手,在Android 4.1之前是雙緩沖機(jī)制,在Android 4.1之后采用三緩沖機(jī)制
- 顯示:SurfaceFlinger玻淑,它將WindowManager提供的所有Surface嗽冒,通過硬件合成器Hardware Composer合成并輸出到顯示屏。
1补履、Android 4.0開啟硬件加速
在Android3.0之前添坊,或者沒有啟用硬件加速時(shí),系統(tǒng)會(huì)使軟件方式渲染UI箫锤,整個(gè)流程如上圖所示:
- Surface贬蛙,每個(gè)View都由某一個(gè)窗口管理雨女,而每個(gè)窗口都關(guān)聯(lián)有一個(gè)Surface
- Canvas,通過Surface的lock函數(shù)獲得一個(gè)Canvas速客,Canvas可以簡單理解為Skia底層接口的封裝
- Graphic Buffer戚篙, SurfaceFlinger會(huì)幫我們托管一個(gè)BufferQueue,我們沖BufferQueue中拿到Graphic Buffer溺职,然后通過Canvas以及Skia將繪制內(nèi)容柵格化到上面
-
SurfaceFlinger岔擂, 通過Swap Buffer 把Front Graphic Buffer 的內(nèi)容交給SurfaceFlinger,最后硬件合成器Hardware Composer合成并輸出到顯示屏浪耘。
Android 3.0開始支持硬件加速乱灵,到Android 4.0 時(shí)默認(rèn)開啟硬件加速
硬件加速繪制與軟件繪制整個(gè)流程差異非常大,最核心就是我們通過GPU完成Graphic Buffer的內(nèi)容繪制七冲。此外硬件繪制還引入了一個(gè)DisplayList的概念痛倚,每個(gè)View內(nèi)部都有一個(gè)DisplayList,當(dāng)某個(gè)View需要重繪時(shí)澜躺,將它標(biāo)記會(huì)Dirty蝉稳。
當(dāng)需要重繪時(shí),僅僅只需要重繪一個(gè)View的DisplayList掘鄙,而不是像軟件繪制那樣需要向上遞歸耘戚。這樣可以大大減少繪圖的操作數(shù)量,提高渲染效率操漠。
2收津、Android 4.1:Project Butter
Project Butter主要包含兩個(gè)組成部分:一個(gè)是VSYNC,一個(gè)是Triple Buffering浊伙。
VSYNC信號(hào)
VSYNC類似于時(shí)鐘中斷撞秋,每收到VSYNC中斷,CPU會(huì)立即準(zhǔn)備Buffer數(shù)據(jù)嚣鄙,由于大部分顯示設(shè)備刷新頻率都是60HZ(一秒刷新60次)吻贿,也是一幀數(shù)據(jù)的準(zhǔn)備工作要在16ms內(nèi)完成。
這樣應(yīng)用總是在VSYNC邊界上開始繪制拗慨,而SurfaceFlinger總是VSYNC邊界上進(jìn)行合成廓八,這樣可以消除卡頓,并提升圖形的視覺表現(xiàn)赵抢。
三級(jí)緩沖機(jī)制Triple Buffering
在Android 4.1之前都使用雙緩沖機(jī)制,不同的View或者Activity它們都會(huì)共用一個(gè)Window声功,也就是共用一個(gè)Surface烦却。而每個(gè)Surface都會(huì)有一個(gè)BufferQueue緩存機(jī)制,但是這個(gè)隊(duì)列會(huì)由SurfaceFlinger管理先巴,通過匿名共享內(nèi)存機(jī)制與App應(yīng)用成交互其爵。
整個(gè)流程如下:
- 每個(gè)Surface對(duì)應(yīng)的BufferQueue內(nèi)部都有兩個(gè)Graphic Buffer冒冬,一個(gè)用于繪制一個(gè)用于顯示。會(huì)把內(nèi)容先繪制到離屏緩沖區(qū)(OffScreen Buffer)摩渺,在需要顯示時(shí)简烤,才把離屏緩沖區(qū)的內(nèi)容通過Swap Buffer復(fù)制到Front Graphic Buffer中
- 這樣SurfaceFlinger就拿到了某個(gè)Surface最終要顯示的內(nèi)容,但是同一時(shí)間可能會(huì)有多個(gè)Surface摇幻,這里可能是不同應(yīng)用的Surface横侦,也可能是同一個(gè)應(yīng)用里面類似SurfaceView和TextureView,他們都會(huì)有自己單獨(dú)的Surface
-
這個(gè)時(shí)候SurfaceFlinger把所有的Surface要顯示的內(nèi)容統(tǒng)一交給Hardware Composer绰姻,它會(huì)根據(jù)位置枉侧、Z-Order順序等信息合成為最終顯示的內(nèi)容,而這個(gè)內(nèi)容交給系統(tǒng)的幀緩沖區(qū)Frame Buffer來顯示(Frame Buffer是非常底層的狂芋,可以理解為屏幕顯示的抽象)榨馁。
如果只有兩個(gè)Graphic Buffer緩沖區(qū)A和B,如果CPU/GPU繪制時(shí)間過程帜矾,超過了一個(gè)VSYNC信號(hào)周期翼虫,因?yàn)榫彌_區(qū)B中的數(shù)據(jù)還沒準(zhǔn)備好,所以只能繼續(xù)顯示A緩沖區(qū)的內(nèi)容屡萤,這樣緩沖區(qū)A和B都分別被顯示設(shè)備和GPU占用珍剑,CPU無法準(zhǔn)備下一幀的數(shù)據(jù)。
如果再提供一個(gè)緩沖區(qū)灭衷,CPU次慢、GPU和顯示設(shè)備都能各自使用各自的緩沖區(qū)工作,互不應(yīng)用翔曲。簡單來說迫像,三緩沖機(jī)制就是在雙緩沖機(jī)制的基礎(chǔ)上增加了一個(gè)Graphic Buffer,這樣可以最大限度的利用空閑時(shí)間瞳遍,帶來的壞處是多使用了一個(gè)Graphic Buffer所占用的內(nèi)存闻妓。
數(shù)據(jù)測(cè)量
Android 4.1新增了Systrace性能數(shù)據(jù)采樣和分析工具,Tracer for OpenGL ES也是Android 4.1新增的工具掠械,可逐幀由缆、逐函數(shù)的記錄App用OpenGL ES的繪制過程。它提供了每個(gè)OpenGL函數(shù)調(diào)用的消耗時(shí)間猾蒂,所以很多時(shí)候用來做性能分析均唉。在Android 4.2系統(tǒng)增加了檢測(cè)過度繪制工具。
3肚菠、Android 5.0:RenderThread
經(jīng)過Project Buffer黃油計(jì)劃之后舔箭,Android的渲染性能有了很大的改善,但是有一個(gè)問題,雖然我們利用了GPU的圖形高性能運(yùn)算层扶,但是從計(jì)算DisplayList箫章,到通過GPU繪制到Frame Buffer,整個(gè)計(jì)算和繪制都在UI主線程中完成镜会。
UI主線程任務(wù)過于繁重檬寂,如果整個(gè)渲染過比較耗時(shí),可能會(huì)造成用戶無法響應(yīng)的操作戳表,進(jìn)而出現(xiàn)卡頓桶至。GPU對(duì)圖形的繪制渲染能力更勝一籌,如果使用GPU并在不同的線程繪制渲染圖形扒袖,那么整個(gè)流程會(huì)更加順暢塞茅。
所以Android 5.0引入了兩個(gè)比較大的改變。一是RenderNode季率,它對(duì)DisplayList及一些View的顯示屬性做了進(jìn)一步的封裝野瘦;另一個(gè)是RenderThread,所欲的GL命令執(zhí)行都放到這個(gè)線程上飒泻,渲染線程在RenderNode中存有渲染幀的所有信息鞭光,可以做一些屬性動(dòng)畫,這樣即便主線程有耗時(shí)的操作也可以保證動(dòng)畫流程泞遗。
我們可以開啟Profile GPU Rendering檢查惰许,在Android 6.0之后,會(huì)輸出下面的計(jì)算和繪制每個(gè)階段的耗時(shí):
如果將上面的步驟轉(zhuǎn)化為線程模型史辙,可以得到下面的流水線模型汹买。CPU將數(shù)據(jù)同步(sync)給GPU之后,一般不會(huì)阻塞等待GPU渲染完畢聊倔,而是通知結(jié)束后返回晦毙,而RenderThread承擔(dān)了比較多的繪制工作,分擔(dān)了主線程很多壓力耙蔑,提高了UI線程的響應(yīng)速度见妒。
UI渲染測(cè)量
- 測(cè)試工具:Profile GPU Rendering 和Show GPU Overdraw
-
問題定位工具:Systrace和Tracer for OpenGL ES
在Android 3.1之后,推薦使用Graphic API Debugger(GAPID)來代替Tracer for OpenGL ES工具甸陌,GAPID可以說是升級(jí)版须揣,它不僅可以跨平臺(tái),而且功能更加強(qiáng)大钱豁,支持Vulkan與回放耻卡。
1、gfxinfo
gfxinfo可以輸出包含各階段發(fā)生的動(dòng)畫及幀相關(guān)的性能信息牲尺,具體命令如下:
adb shell dumpsys gfxinfo 包名
除了渲染性能之外劲赠,gfxinfo還可以拿到渲染相關(guān)的內(nèi)存和View hierachy信息,在Android 6.0之后秸谢,gfxinfo命令增加了framestats參數(shù)凛澎,可以拿到最近120幀每個(gè)繪制階段的耗時(shí)信息。通過這個(gè)命令可以實(shí)現(xiàn)自動(dòng)化統(tǒng)計(jì)應(yīng)用的幀率估蹄,更進(jìn)一步還可以實(shí)現(xiàn)自定義的“Profile GPU Rendering”工具塑煎。
adb shell dumpsys gfxinfo 包名 framestats
2、SurfaceFlinger
關(guān)于渲染使用的內(nèi)存情況臭蚁,可以使用下面的命令拿到SurfaceFlinger相關(guān)的信息:
adb shell dumpsys SurfaceFlinger
UI優(yōu)化的常用手段
要求所有的渲染操作都必須在16ms(=1000ms / 60fps)內(nèi)完成最铁,UI優(yōu)化就是拆解熏染的各個(gè)階段的耗時(shí),找到瓶頸點(diǎn)垮兑,再加以優(yōu)化冷尉。
1、盡量使用硬件加速
硬件加速繪制的性能是遠(yuǎn)大于軟件繪制系枪,所以要盡可能的采用硬件加速雀哨。有些情況不能采用硬件加速,因?yàn)橛布铀俨恢С炙械腃anvas API私爷,具體API兼容列表雾棺,查看drawing-support,如果使用了不支持的API衬浑,系統(tǒng)就需要通過CPU軟件模擬繪制捌浩,這也是漸變、磨砂工秩、圓角等效果渲染性能比較低的原因尸饺。
SVG也是一個(gè)非常典型的例子,SVG很多指令硬件加速都不支持助币,但可以提前將SVG轉(zhuǎn)換成Bitmap保存起來浪听,這樣系統(tǒng)就可以更好的使用硬件加速會(huì)繪制,同理對(duì)于圓角奠支、漸變等場景馋辈,也可以改為Bitmap實(shí)現(xiàn)。
2倍谜、Create View優(yōu)化
View的創(chuàng)建是在主線程完成的迈螟,對(duì)于一些負(fù)責(zé)的界面,耗時(shí)會(huì)增加尔崔,根據(jù)View的創(chuàng)建流程有如下優(yōu)化點(diǎn):
使用代碼創(chuàng)建
XML進(jìn)行UI編寫十分方便且提供可視化預(yù)覽答毫,但是效率方面大打折扣,建議在對(duì)性能要求非常高季春,且修改不頻繁的頁面采用代碼創(chuàng)建的方式洗搂。
異步創(chuàng)建
如果再線程提前創(chuàng)建View,會(huì)報(bào)如下的錯(cuò)誤:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() at android.os.Handler.(Handler.java:121)
解決方案是先將線程的Looper的MessageQueue替換成UI線程Looper的Queue,最后在創(chuàng)建完View后把線程的Looper恢復(fù)成原來的耘拇。
View復(fù)用
正常來說撵颊,View會(huì)隨著Activity的銷毀而同時(shí)銷毀,ListView惫叛、RecycleView通過View的緩存和重用大大提升渲染性能倡勇。因此可以實(shí)現(xiàn)一套可以在不同Activity或Fragment使用的View緩存機(jī)制。但是這里需要保證所有進(jìn)入緩存的View不會(huì)保留之前的狀態(tài)嘉涌。
3妻熊、measure/layout優(yōu)化
- 減少UI布局層次,盡量扁平化仑最,使用<ViewStub><Merg>等優(yōu)化
- 優(yōu)化layout開銷扔役,盡量不使用RelativeLayout或者基于weighted LinearLayout,他們layout的開銷非常巨大警医,建議使用ConstrainLayout替代亿胸。
- 背景優(yōu)化,盡量不要重復(fù)設(shè)置背景
PrecomputedText提供了接口法严,可以異步進(jìn)行measure和layout损敷,不必再主線程中執(zhí)行。
UI優(yōu)化其他手段
1深啤、Litho:異步布局
Litho是Facebook開源的聲明式Android UI渲染框架拗馒,本身非常強(qiáng)大,內(nèi)部做了很多優(yōu)化溯街。
-
異步布局
一般來說Android所有的控件繪制都要遵守measure -> layout -> draw的流水線诱桂。
Litho把measure和layout都放到了后臺(tái)線程,只留下了必須要再主線程完成的draw呈昔,大大降低了UI線程的負(fù)載挥等。
- 界面扁平化
Litho使用自有的布局引擎(Yoga),在布局階段可以檢測(cè)不必要的層級(jí)堤尾,減少ViewGroups肝劲,來實(shí)現(xiàn)UI扁平化。 -
優(yōu)化RecycleView
Litho優(yōu)化RecycleView中UI組件的緩存和回收方法郭宝,原生RecycleView或者ListView是按照viewType來進(jìn)行緩存和回收辞槐,但是如果一個(gè)RecycleView/ListView中出現(xiàn)過多的viewType,會(huì)使緩存形同虛設(shè)粘室,Litho是按照text榄檬、image和video獨(dú)立回收的,可以提高緩存命中率衔统、降低內(nèi)存使用率鹿榜、提高滾動(dòng)幀率海雪。
2、Flutter:自己的布局+渲染引擎
Flutter是谷歌推出的開源移動(dòng)應(yīng)用開發(fā)框架舱殿,可發(fā)著可以通過Dart語言開發(fā)App奥裸,一套代碼同時(shí)運(yùn)行在iOS和Android平臺(tái)。在Android上Flutter完全沒有基于系統(tǒng)的渲染引擎怀薛,而是把Skia引擎直接集成到App中刺彩,并且直接使用了Dart虛擬機(jī)。
開發(fā)Flutter應(yīng)用總的來說簡化了線程模型枝恋,框架給我們抽象出各司其職的Runner,包括UI嗡害、GPU焚碌、I/O、Platform Runner霸妹。Android平臺(tái)上面沒一個(gè)引擎實(shí)例啟動(dòng)的時(shí)候都會(huì)為UI Runner十电、GPU Runner、I/O Runner各自創(chuàng)建一個(gè)新的線程叹螟,所有Engine實(shí)例共享同一個(gè)Platform Runner和線程鹃骂。
- 首先UI Runner會(huì)執(zhí)行root isolate(可以簡單理解為main函數(shù),isolate是Dart虛擬機(jī)中一種執(zhí)行并發(fā)代碼實(shí)現(xiàn)罢绽,Dart虛擬機(jī)實(shí)現(xiàn)了Actor的并發(fā)模型畏线,與大名鼎鼎的Erlang使用了類似的并發(fā)模型。)
- Flutter 引擎得到通知后良价,會(huì)告知系統(tǒng)我們要同步VSYNC
- 得到GPU的VSYNC信號(hào)后寝殴,對(duì)UI Widgets進(jìn)行Layout并生成一個(gè)Layer Tree
- 然后Layer Tree會(huì)交給GPU Runner進(jìn)行合成和柵格化
-
GPU Runner使用Skia庫繪制相關(guān)圖形
Flutter也采用了類似Litho、React屬性不可變明垢,單向數(shù)據(jù)流的方案蚣常,這樣做的好處是將視圖與數(shù)據(jù)分開。
3痊银、RenderThread與RenderScript
在Android 5.0系統(tǒng)增加了RenderThread對(duì)于ViewPropertyAnimator和CircularReveal動(dòng)畫抵蚊,可以使用RenderThread實(shí)現(xiàn)動(dòng)畫的異步渲染。圖片的變換涉及大量的計(jì)算任務(wù)溯革,可以通過RenderScript提高性能贞绳,它是Android操作系統(tǒng)提供的一套API,基于異構(gòu)計(jì)算思想鬓照,專門用于密集計(jì)算熔酷。RenderScript提供了三個(gè)基本工具:一個(gè)硬件無關(guān)的通用計(jì)算API;一個(gè)類似于CUDA豺裆、OpenGL和GLSL的計(jì)算API拒秘;一個(gè)類C99的腳本語言号显,語序開發(fā)者以較少的代碼實(shí)現(xiàn)功能復(fù)雜且性能優(yōu)越的應(yīng)用程序。