Android頁(yè)面渲染效率優(yōu)化實(shí)踐
?1.車系頁(yè)布局渲染現(xiàn)狀
車系頁(yè)是重要的車系信息頁(yè)面,更新迭代多年,頁(yè)面布局不斷變化,xml布局文件越寫越復(fù)雜。
獲取車系頁(yè)布局文件耗時(shí):
結(jié)果如下:
2.卡頓的原因
2.1Android繪制原理
? 1.Android的屏幕刷新中涉及到最重要的三個(gè)概念
(1)CPU:執(zhí)行應(yīng)用層的measure、layout、draw等操作,繪制完成后將數(shù)據(jù)提交給GPU
(2)GPU:進(jìn)一步處理數(shù)據(jù),并將數(shù)據(jù)緩存起來(lái)
(3)屏幕:由一個(gè)個(gè)像素點(diǎn)組成,以固定的頻率(16.6ms,即1秒60幀)從緩沖區(qū)中取出數(shù)據(jù)來(lái)填充像素點(diǎn)
總結(jié)一句話就是:CPU 繪制后提交數(shù)據(jù)、GPU 進(jìn)一步處理和緩存數(shù)據(jù)、最后屏幕從緩沖區(qū)中讀取數(shù)據(jù)并顯示。
? 2.雙緩沖機(jī)制
當(dāng)布局比較復(fù)雜,或設(shè)備性能較差的時(shí)候,CPU并不能保證在16.6ms內(nèi)就完成繪制數(shù)據(jù)的計(jì)算,所以這里系統(tǒng)又做了一個(gè)處理。
當(dāng)你的應(yīng)用正在往Back Buffer中填充數(shù)據(jù)時(shí),系統(tǒng)會(huì)將Back Buffer鎖定。
如果到了GPU交換兩個(gè)Buffer的時(shí)間點(diǎn),你的應(yīng)用還在往Back Buffer中填充數(shù)據(jù),GPU會(huì)發(fā)現(xiàn)Back Buffer被鎖定了,它會(huì)放棄這次交換。
這樣做的后果就是手機(jī)屏幕仍然顯示原先的圖像,這就是我們常常說(shuō)的掉幀。
2.2布局加載原理
頁(yè)面啟動(dòng)時(shí),布局加載在主線程上進(jìn)行耗時(shí)操作,會(huì)導(dǎo)致頁(yè)面渲染及加載慢。
布局加載主要通過setContentView來(lái)實(shí)現(xiàn),下面是它的調(diào)用時(shí)序圖:
我們可以看到,在setContentView中主要有兩個(gè)耗時(shí)操作:
(1)解析xml,獲取XmlResourceParser,這是IO過程。
(2)通過createViewFromTag,創(chuàng)建View對(duì)象,用到了反射。
以上兩點(diǎn)就是布局加載慢的原因,也是布局的性能瓶頸。
3.布局加載優(yōu)化
上一章分析了布局加載慢的主要原因,因此,我們的優(yōu)化方式主要有以下兩種:
(1)異步加載,將布局加載過程轉(zhuǎn)移到子線程
(2)去掉IO和反射過程
3.1異步加載,AsyncLayoutInflater方案
setContentView 默認(rèn)是在UI主線程加載布局的,其加載過程中的耗時(shí)操作,如解析xml,反射創(chuàng)建view對(duì)象等也是在主線程執(zhí)行,AsyncLayoutInflater 可以讓這些加載過程在子線程中執(zhí)行,這樣可以提高UI線程的響應(yīng)性,UI線程同時(shí)可以進(jìn)行其他操作。AsyncLayoutInflater使用方式如下:
AsyncLayoutInflater方案的缺點(diǎn):
(1) UI布局和view的初始化在子線程中進(jìn)行,如果view還未初始化成功,在主線程中再調(diào)用view會(huì)引起崩潰。
(2) 一般情況下,主線程會(huì)調(diào)用view,涉及到大量子線程和主線程在view調(diào)用上的同步問題,這就犧牲了易用性,代碼可維護(hù)性也會(huì)變差。
(3) 如果是在老頁(yè)面邏輯結(jié)構(gòu)上引入AsyncLayoutInflater進(jìn)行改造,結(jié)構(gòu)改動(dòng)很大,很容易發(fā)生view調(diào)用崩潰錯(cuò)誤,不太可行。
3.2X2C方案
X2C 是掌閱開源的一套布局加載框架。X2C的主要思路是利用apt工具,在編譯期將我們寫的xml布局文件解析成view,并根據(jù)xml動(dòng)態(tài)設(shè)置view的各類屬性,這樣,我們?cè)谶\(yùn)行時(shí),調(diào)用findViewById,根據(jù)view id拿到的view,已經(jīng)是直接new 出來(lái)的view,避免了運(yùn)行時(shí)的xml IO操作和反射操作,這就解決了布局時(shí)的耗時(shí)問題。
原始的xml布局文件:
X2C 編譯期apt生成的java文件:
X2c的優(yōu)點(diǎn):?
(1)易用性和可維護(hù)性好,對(duì)原有代碼侵入性不強(qiáng),應(yīng)用代碼還是使用xml寫布局
(2)加載耗時(shí)可縮短到原來(lái)的1/2到1/3
X2c的缺點(diǎn):?
(1)View的屬性支持不完全
(2)兼容性和穩(wěn)定性不是很高,在高版本的gradle 編譯工具,如gradle3.1.4,會(huì)出現(xiàn)找不到R.java文件,找不到xml對(duì)應(yīng)的java文件等問題
(3)目前,X2C更新到2021年,并沒有持續(xù)維護(hù)和解決issue
3.3Compose方案
Compose 是 Jetpack 中的一個(gè)新成員,是 Android 團(tuán)隊(duì)在2019年I/O大會(huì)上公布的新的UI庫(kù)。
Compose使用純kotlin開發(fā),使用簡(jiǎn)潔方便,但它是完全拋棄了View 和 ViewGroup這套系統(tǒng),自己把整個(gè)的渲染機(jī)制從里到外做了個(gè)全新的,是未來(lái)取代XML的官方方案。
Compose的優(yōu)點(diǎn):
(1)使用聲明式UI,摒棄了xml布局運(yùn)行時(shí)解析,布局效率更高
(2)使用kotlin開發(fā),簡(jiǎn)單易用,布局形式上跟flutter統(tǒng)一。
如果是使用kotlin開發(fā)的新項(xiàng)目,可以引入Compose方案,對(duì)于老項(xiàng)目的優(yōu)化,Compose方案并不適用。
3.4我們的優(yōu)化方案-在布局反射上做文章
Xml解析到view,完全自己來(lái)做,比較復(fù)雜且有很多風(fēng)險(xiǎn),這個(gè)過程涉及到兩個(gè)耗時(shí)的點(diǎn):
(1)xml解析,IO操作
(2)反射
xml解析這部分工作復(fù)雜度很高,可以交給android系統(tǒng)來(lái)做。我們可以想辦法去除反射的邏輯。
我們需要找到一個(gè)反射生成view的入口。我們知道,View生成相關(guān)邏輯在LayoutInflater的createViewFromTag中,調(diào)用了onCreateView(parent, name, context, attrs),通過反射生成了view。
通過android系統(tǒng)的LayoutInflater setFactory,我們不僅可以控制View的生成,還可以把View變成另外一個(gè)View。在setFactory的onCreateView(parent, name, context, attrs)回調(diào)中,我們接管單個(gè)view的生成,去掉反射,new 出我們自己的view就解決了問題。而onCreateView(parent, name, context, attrs)中的參數(shù)name返回的就是xml中使用到的view的名字,根據(jù)這個(gè)name,直接new出來(lái)新的view。方式如下:
包括系統(tǒng)view和我們自定義的view。
此方案對(duì)已有項(xiàng)目的代碼侵入性很小,改造成本低,兼容性也很高,相對(duì)來(lái)講,在渲染效率上比X2C方案低一些,但比較匹配我們對(duì)已有舊項(xiàng)目復(fù)雜布局的渲染優(yōu)化。
3.5進(jìn)一步在布局上優(yōu)化
我們可以使用viewStub實(shí)現(xiàn)布局的懶加載。思路是將布局分成不同的模塊,讓部分模塊使用viewStub標(biāo)簽替代,一半屏幕的模塊元素渲染完成以后,再通過viewStub來(lái)渲染生成viewStub所包含的其它模塊,實(shí)現(xiàn)延遲渲染加載。
通過分析車系頁(yè)布局,已經(jīng)將布局元素,按功能做了一些模塊的劃分,我們進(jìn)一步將關(guān)聯(lián)度大的布局模塊集中在一起,封裝在一個(gè)自定義VIEW中,使用viewStub包含替換這些模塊View。UI線程setContentView渲染布局時(shí),viewStub所包含的模塊并不會(huì)被渲染,只會(huì)渲染屏幕的部分元素,等待主接口數(shù)據(jù)返回,再使用viewStub延遲其它模塊,實(shí)現(xiàn)了布局的懶加載,加快了主線程的渲染速度。
4.優(yōu)化結(jié)果
通過3.4和3.5節(jié)的優(yōu)化方法,車系頁(yè)復(fù)雜布局渲染優(yōu)化對(duì)比結(jié)果如下:
通過對(duì)比可以看到,在不同檔次的android機(jī)型上,渲染耗時(shí)降低了20%-35%左右,在低端機(jī)型上,減少的絕對(duì)耗時(shí)更多,感受可能會(huì)明顯一些。
作者簡(jiǎn)介
蔣雄鋒
■ 經(jīng)銷商事業(yè)部-經(jīng)銷商技術(shù)部。
■ 2018年加入汽車之間,目前任職經(jīng)銷商技術(shù)部移動(dòng)App團(tuán)隊(duì),主要涉及Android移動(dòng)端,F(xiàn)lutter,React Native等大前端技術(shù),負(fù)責(zé)汽車報(bào)價(jià)App業(yè)務(wù)的開發(fā)。