總結(jié)Android渲染部分的工作原理,其中參考了如下網(wǎng)址:
http://www.androidpolice.com/2012/07/12/getting-to-know-android-4-1-part-3-project-butter-how-it-works-and-what-it-added/
http://blog.csdn.net/michaelcao1980/article/details/43233765
https://en.wikipedia.org/wiki/Screen_tearing
http://hukai.me/android-performance-render/
1)基本概念
在一個(gè)典型的顯示系統(tǒng)中,一般包括CPU、GPU、display三個(gè)部分, CPU負(fù)責(zé)計(jì)計(jì)算數(shù)據(jù),把計(jì)算好數(shù)據(jù)交給GPU,GPU會(huì)對圖形數(shù)據(jù)進(jìn)行渲染,渲染好后放到buffer里存起來,然后display(有的文章也叫屏幕或者顯示器)負(fù)責(zé)把buffer里的數(shù)據(jù)呈現(xiàn)到屏幕上。很多時(shí)候,我們可以把CPU、GPU放在一起說,那么就是包括2部分,CPU/GPU 和display(本文主要按后面這種分類來解釋)。
tearing: 一個(gè)屏幕內(nèi)的數(shù)據(jù)來自2個(gè)不同的幀,畫面會(huì)出現(xiàn)撕裂感。
jank: 一個(gè)幀在屏幕上連續(xù)出現(xiàn)2次。
lag:從用戶體驗(yàn)來說,就是點(diǎn)擊下去到呈現(xiàn)效果之間存在延遲。
Refresh Rate:代表了屏幕在一秒內(nèi)刷新屏幕的次數(shù),這取決于硬件的固定參數(shù),例如60Hz。
Frame Rate:代表了GPU在一秒內(nèi)繪制操作的幀數(shù),例如30fps,60fps。
2)Screen tearing問題如何解決
screen tearing
顯示過程,簡單的說就是CPU/GPU準(zhǔn)備好數(shù)據(jù),存入buffer,display去buffer里取數(shù)據(jù),然后顯示出來。如果只有一個(gè)buffer,那么這個(gè)buffer既被GPU寫,也同時(shí)被display讀,如果讀的速度跟寫的速度一樣,那可以正常顯示。如果讀的比寫的快(顯示器刷新頻率略快于CPU/GPU準(zhǔn)備緩存的速度),那也沒什么問題。但是如果讀的比寫的慢的話,很可能有buffer里的數(shù)據(jù)沒有被讀取,就被重寫了,這樣相當(dāng)于一部分?jǐn)?shù)據(jù)丟失了,這是不允許的。比如display在讀取幀1的過程中(為了顯示幀1),CPU/GPU把幀2寫到了buffer里 ,而display并不知道此時(shí)buffer里已經(jīng)是幀2了,那么就會(huì)出現(xiàn)display讀的上半部分是幀1,下半部分是2的, 出現(xiàn)畫面“割裂”,這就叫tearing。
double-buffer
tearing發(fā)生的原因是display讀buffer時(shí),buffer被修改,那么多一個(gè)buffer是不是能解決問題,是的,事實(shí)上目前所有的顯示系統(tǒng)都是雙緩存的,單緩存存在于30年前。
雙緩沖技術(shù),基本原理就是采用兩塊buffer。一塊back buffer用于CPU/GPU后臺(tái)繪制,另一塊framebuffer則用于顯示,當(dāng)back buffer準(zhǔn)備就緒后,它們才進(jìn)行交換。不可否認(rèn),doublebuffering可以在很大程度上降低screen tearing錯(cuò)誤,但是它是萬能的嗎?
一個(gè)需要考慮的問題是我們什么時(shí)候進(jìn)行兩個(gè)緩沖區(qū)的交換呢?假如是back buffer準(zhǔn)備完成一幀數(shù)據(jù)以后就進(jìn)行,那么如果此時(shí)屏幕還沒有完整顯示上一幀內(nèi)容的話,肯定是會(huì)出問題的。看來只能是等到屏幕處理完一幀數(shù)據(jù)后,才可以執(zhí)行這一操作了。
我們知道,一個(gè)典型的顯示器有兩個(gè)重要特性,行頻和場頻。行頻(HorizontalScanningFrequency)又稱為“水平掃描頻率”,是屏幕每秒鐘從左至右掃描的次數(shù); 場頻(Vertical Scanning Frequency)也稱為“垂直掃描頻率”,是每秒鐘整個(gè)屏幕刷新的次數(shù)。由此也可以得出它們的關(guān)系:行頻=場頻*縱坐標(biāo)分辨率。
當(dāng)掃描完一個(gè)屏幕后,設(shè)備需要重新回到第一行以進(jìn)入下一次的循環(huán),此時(shí)有一段時(shí)間空隙,稱為VerticalBlanking Interval(VBI)。大家應(yīng)該能想到了,這個(gè)時(shí)間點(diǎn)就是我們進(jìn)行緩沖區(qū)交換的最佳時(shí)間。因?yàn)榇藭r(shí)屏幕沒有在刷新,也就避免了交換過程中出現(xiàn)screentearing的狀況。VSync(垂直同步)是VerticalSynchronization的簡寫,它利用VBI時(shí)期出現(xiàn)的vertical sync pulse來保證雙緩沖在最佳時(shí)間點(diǎn)才進(jìn)行交換。
總結(jié)
Screen Tearing出現(xiàn)的原因有兩個(gè):
1,單緩沖情況下,在display的時(shí)候draw(也就是所謂的On Display Draw)
2,雙緩沖情況下,在display的時(shí)候swap buffer(Flip).
所以:
我們只要使用雙緩沖做Flip(避免了On display Draw),并且做VSync同步,即每次等到VSync階段再做swap,就可以完美解決Screen Tearing問題。
3)VSYNC的前生今世
VSYNC(Vertical Synchronization)是一個(gè)相當(dāng)古老的概念,對于游戲玩家,它有一個(gè)更加大名鼎鼎的中文名字—-垂直同步。“垂直同步(vsync)”指的是顯卡的輸出幀數(shù)和屏幕的垂直刷新率相同,這完全是一個(gè)CRT顯示器上的概念。其實(shí)無論是VSYNC還是垂直同步這個(gè)名字,因?yàn)長CD根本就沒有垂直掃描的這種東西,因此這個(gè)名字本身已經(jīng)沒有意義。但是基于歷史的原因,這個(gè)名稱在圖形圖像領(lǐng)域被沿襲下來。在當(dāng)下,垂直同步的含義我們可以理解為,使得顯卡生成幀的速度和屏幕刷新的速度的保持一致。舉例來說,如果屏幕的刷新率為60Hz,那么生成幀的速度就應(yīng)該被固定在1/60 s。
Android中的VSYNC — 黃油計(jì)劃
從Android 4.1開始,谷歌致力于解決Android系統(tǒng)中最飽受詬病的一個(gè)問題,滑動(dòng)不如iOS流暢。因谷歌在4.1版本引入了一個(gè)重大的改進(jìn)—Project Butter,也即是黃油計(jì)劃。Project Butter對Android Display系統(tǒng)進(jìn)行了重構(gòu),引入了三個(gè)核心元素,即VSYNC、Triple Buffer和Choreographer。關(guān)于后面兩個(gè)概念我們會(huì)在后面開專題講解,這里我們重點(diǎn)講解VSYNC的作用。玩過大型PC游戲的玩家都知道,VSYNC最重要的作用是防止出現(xiàn)畫面撕裂(screentearing)。所謂畫面撕裂,就是指一個(gè)畫面上出現(xiàn)了兩幀畫面的內(nèi)容,如下圖。
為什么會(huì)出現(xiàn)這種情況呢?這種情況一般是因?yàn)轱@卡輸出幀的速度高于顯示器的刷新速度,導(dǎo)致顯示器并不能及時(shí)處理輸出的幀,而最終出現(xiàn)了多個(gè)幀的畫面都留在了顯示器上的問題。這也就是我們所說的畫面撕裂。
提到垂直同步這里就多提一句,其實(shí)我認(rèn)為對于PC上的大型游戲來說,只有配置足夠高,高到顯卡輸出幀率可以穩(wěn)定的高于顯示器的刷新頻率,才有開啟垂直同步的必要。因?yàn)橹挥羞@個(gè)時(shí)候,畫面撕裂才會(huì)真正成為一個(gè)問題。而對于很多情況下主機(jī)性能不足導(dǎo)致游戲輸出幀率低于顯示器的刷新頻率的情況下,尤其是幀率穩(wěn)定在40~60之間時(shí),開啟垂直同步可能會(huì)導(dǎo)致幀率倍數(shù)級(jí)的下降(具體原因我們在Graphic架構(gòu)一文中提到過,當(dāng)幀生成速度不及VSync速度時(shí),幀率的下降不是平緩的,而且很可能是倍數(shù)級(jí)的。當(dāng)然這在android系統(tǒng)上并非嚴(yán)重問題,因?yàn)閍ndroid上很少有高速的復(fù)雜場景的頻繁切換。事實(shí)上,在Android的普通應(yīng)用場景下,VSync的使用不僅不會(huì)降低幀率,還可以有效解決卡頓問題)。
4)Jank問題,triple buffer
如果沒有VSync同步, 繪圖速度大于顯示速度那么會(huì)有問題(screen tearing).
如果沒有VSync同步,還有另一個(gè)問題,就是反過來繪圖速度過慢的時(shí)候.
如圖::
這個(gè)圖中有三個(gè)元素,Display是顯示屏幕,GPU和CPU負(fù)責(zé)渲染幀數(shù)據(jù),每個(gè)幀以方框表示,并以數(shù)字進(jìn)行編號(hào),如0、1、2等等。VSync用于指導(dǎo)雙緩沖區(qū)的交換。以時(shí)間的順序來看下將會(huì)發(fā)生的異常:
Step1. Display顯示第0幀數(shù)據(jù),此時(shí)CPU和GPU渲染第1幀畫面,而且趕在Display顯示下一幀前完成。
Step2. 因?yàn)殇秩炯皶r(shí),Display在第0幀顯示完成后,也就是第1個(gè)VSync后,正常顯示第1幀。
Step3. 由于某些原因,比如CPU資源被占用,系統(tǒng)沒有及時(shí)地開始處理第2幀,直到第2個(gè)VSync快來前才開始處理
Step4. 第2個(gè)VSync來時(shí),由于第2幀數(shù)據(jù)還沒有準(zhǔn)備就緒,顯示的還是第1幀。這種情況被Android開發(fā)組命名為“Jank”。
Step5. 當(dāng)?shù)?幀數(shù)據(jù)準(zhǔn)備完成后,它并不會(huì)馬上被顯示,而是要等待下一個(gè)VSync。所以總的來說,就是屏幕平白無故地多顯示了一次第1幀。原因大家應(yīng)該都看到了,就是CPU沒有及時(shí)地開始著手處理第2幀的渲染工作,以致“延誤軍機(jī)”。
其實(shí)總結(jié)上面的這個(gè)情況之所以發(fā)生,首先的原因就在于第二幀沒有及時(shí)的繪制。那么如何使得第二幀及時(shí)被繪制呢?這就是我們在Graphic系統(tǒng)中引入VSYNC的原因,考慮下面這張圖:
如上圖所示,一旦VSync出現(xiàn)后,立刻就開始執(zhí)行下一幀的繪制工作。這樣就可以大大降低Jank出現(xiàn)的概率。另外,VSYNC引入后,要求繪制也只能在收到VSYNC消息之后才能進(jìn)行,因此,也就杜絕了另外一種極端情況的出現(xiàn)—-CPU(GPU)一直不停的進(jìn)行繪制,幀的生成速度高于屏幕的刷新速度,導(dǎo)致生成的幀不能被顯示,只能丟棄,這樣就出現(xiàn)了丟幀的情況—-引入VSYNC后,繪制的速度就和屏幕刷新的速度保持一致了。
大部分的Android顯示設(shè)備刷新率是60Hz,這也就意味著每一幀最多只能有1/60=16ms左右的準(zhǔn)備時(shí)間。
假如CPU/GPU的FPS(FramesPer Second)高于這個(gè)值,那么這個(gè)方案是完美的,顯示效果將很好。可是我們沒有辦法保證所有設(shè)備的硬件配置都能達(dá)到要求。假如CPU/GPU的性能無法滿足上圖的條件,又是什么情況呢?
這個(gè)圖采用了雙緩沖,以及前面介紹的VSync,可以看到整個(gè)過程還是相當(dāng)不錯(cuò)的,雖然CPU/GPU處理所用的時(shí)間時(shí)短時(shí)長,但總的來說都在16ms以內(nèi),因而不影響顯示效果。A和B分別代表兩個(gè)緩沖區(qū),它們不斷地交換來正確顯示畫面。
現(xiàn)在我們可以繼續(xù)分析FPS低于屏幕刷新率的情況,如圖所示:
當(dāng)CPU/GPU的處理時(shí)間超過16ms時(shí),第一個(gè)VSync到來時(shí),緩沖區(qū)B中的數(shù)據(jù)還沒有準(zhǔn)備好,于是只能繼續(xù)顯示之前A緩沖區(qū)中的內(nèi)容。而B完成后,又因?yàn)槿狈Sync pulse信號(hào),它只能等待下一個(gè)signal的來臨。于是在這一過程中,有一大段時(shí)間是被浪費(fèi)的。當(dāng)下一個(gè)VSync出現(xiàn)時(shí),CPU/GPU馬上執(zhí)行操作,此時(shí)它可操作的buffer是A,相應(yīng)的顯示屏對應(yīng)的就是B。這時(shí)看起來就是正常的。只不過由于執(zhí)行時(shí)間仍然超過16ms,導(dǎo)致下一次應(yīng)該執(zhí)行的緩沖區(qū)交換又被推遲了——如此循環(huán)反復(fù),便出現(xiàn)了越來越多的“Jank”。
那么有沒有規(guī)避的辦法呢?
很顯然,第一次的Jank看起來是沒有辦法的,除非升級(jí)硬件配置來加快FPS。我們關(guān)注的重點(diǎn)是被CPU/GPU浪費(fèi)的時(shí)間段,怎么才能充分利用起來呢?分析上述的過程,造成CPU/GPU無事可做的假象是因?yàn)楫?dāng)前已經(jīng)沒有可用的buffer了。換句話說,如果增加一個(gè)buffer,情況會(huì)不會(huì)好轉(zhuǎn)呢?如圖:
Triple Buffering是MultipleBuffering的一種,指的是系統(tǒng)使用3個(gè)緩沖區(qū)用于顯示工作。我們來逐步分析下這個(gè)新機(jī)制是否有效。首先和預(yù)料中的一致,第一次“Jank”無可厚非。不過讓人欣慰的是,當(dāng)?shù)谝淮蜼Sync發(fā)生后,CPU不用再等待了,它會(huì)使用第三個(gè)buffer C來進(jìn)行下一幀數(shù)據(jù)的準(zhǔn)備工作。雖然對緩沖區(qū)C的處理所需時(shí)間同樣超過了16ms,但這并不影響顯示屏——第2次VSync到來后,它選擇buffer B進(jìn)行顯示;而第3次VSync時(shí),它會(huì)接著采用C,而不是像double buffering中所看到的情況一樣只能再顯示一遍了。這樣子就有效地降低了系統(tǒng)顯示錯(cuò)誤的機(jī)率。
注意:triple Bufferring好像完美解決了連續(xù)jank問題,但是第一次的jank無法避免,而且還有l(wèi)ag問題,例如緩存區(qū)C的內(nèi)容要16ms以后才能顯示。
5)用戶為什么會(huì)感覺到卡頓
根本原因
大多數(shù)用戶感知到的卡頓等性能問題的最主要根源都是因?yàn)殇秩拘阅堋脑O(shè)計(jì)師的角度,他們希望App能夠有更多的動(dòng)畫,圖片等時(shí)尚元素來實(shí)現(xiàn)流暢的用戶體驗(yàn)。但是Android系統(tǒng)很有可能無法及時(shí)完成那些復(fù)雜的界面渲染操作。Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào),觸發(fā)對UI進(jìn)行渲染,如果每次渲染都成功,這樣就能夠達(dá)到流暢的畫面所需要的60fps,為了能夠?qū)崿F(xiàn)60fps,這意味著程序的大多數(shù)操作都必須在16ms內(nèi)完成。
如果你的某個(gè)操作花費(fèi)時(shí)間是24ms,系統(tǒng)在得到VSYNC信號(hào)的時(shí)候就無法進(jìn)行正常渲染,這樣就發(fā)生了丟幀現(xiàn)象。那么用戶在32ms內(nèi)看到的會(huì)是同一幀畫面。
用戶容易在UI執(zhí)行動(dòng)畫或者滑動(dòng)ListView的時(shí)候感知到卡頓不流暢,是因?yàn)檫@里的操作相對復(fù)雜,容易發(fā)生丟幀的現(xiàn)象,從而感覺卡頓。有很多原因可以導(dǎo)致丟幀,也許是因?yàn)槟愕膌ayout太過復(fù)雜,無法在16ms內(nèi)完成渲染,有可能是因?yàn)槟愕腢I上有層疊太多的繪制單元,還有可能是因?yàn)閯?dòng)畫執(zhí)行的次數(shù)過多。這些都會(huì)導(dǎo)致CPU或者GPU負(fù)載過重。
渲染過程CPU和GPU的分工
渲染操作通常依賴于兩個(gè)核心組件:CPU與GPU。CPU負(fù)責(zé)包括Measure,Layout,Record,Execute的計(jì)算操作,GPU負(fù)責(zé)Rasterization(柵格化)操作。CPU通常存在的問題的原因是存在非必需的視圖組件,它不僅僅會(huì)帶來重復(fù)的計(jì)算操作,而且還會(huì)占用額外的GPU資源。
了解Android是如何利用GPU進(jìn)行畫面渲染有助于我們更好的理解性能問題。一個(gè)很直接的問題是:activity的畫面是如何繪制到屏幕上的?那些復(fù)雜的XML布局文件又是如何能夠被識(shí)別并繪制出來的?
Resterization柵格化是繪制那些Button,Shape,Path,String,Bitmap等組件最基礎(chǔ)的操作。它把那些組件拆分到不同的像素上進(jìn)行顯示。這是一個(gè)很費(fèi)時(shí)的操作,GPU的引入就是為了加快柵格化的操作。
CPU負(fù)責(zé)把UI組件計(jì)算成Polygons,Texture紋理,然后交給GPU進(jìn)行柵格化渲染。
然而每次從CPU轉(zhuǎn)移到GPU是一件很麻煩的事情,所幸的是OpenGL ES可以把那些需要渲染的紋理Hold在GPU Memory里面,在下次需要渲染的時(shí)候直接進(jìn)行操作。所以如果你更新了GPU所hold住的紋理內(nèi)容,那么之前保存的狀態(tài)就丟失了。
在Android里面那些由主題所提供的資源,例如Bitmaps,Drawables都是一起打包到統(tǒng)一的Texture紋理當(dāng)中,然后再傳遞到GPU里面,這意味著每次你需要使用這些資源的時(shí)候,都是直接從紋理里面進(jìn)行獲取渲染的。當(dāng)然隨著UI組件的越來越豐富,有了更多演變的形態(tài)。例如顯示圖片的時(shí)候,需要先經(jīng)過CPU的計(jì)算加載到內(nèi)存中,然后傳遞給GPU進(jìn)行渲染。文字的顯示比較復(fù)雜,需要先經(jīng)過CPU換算成紋理,然后交給GPU進(jìn)行渲染,返回到CPU繪制單個(gè)字符的時(shí)候,再重新引用經(jīng)過GPU渲染的內(nèi)容。動(dòng)畫則存在一個(gè)更加復(fù)雜的操作流程。
為了能夠使得App流暢,我們需要在每幀16ms以內(nèi)處理完所有的CPU與GPU的計(jì)算,繪制,渲染等等操作。
后面章節(jié)引言:
前幾章講解的很多display相關(guān)的概念,也描述了Android一些機(jī)制來盡量保證顯示流暢。
double buffering 和VSync解決了 screen tearing 問題。
Triple buffering解決了連續(xù)jank問題,但是第一次的jank還是無法避免,而且引入了lag問題,用戶還是會(huì)感覺到卡頓。
假設(shè)硬件不升級(jí)的情況下,如果我們系統(tǒng)能在16ms之內(nèi)完成所有渲染工作,那這些問題都從根本上解決(理想很豐滿,現(xiàn)實(shí)很骨感)。我們APP編寫者還是要節(jié)約資源,避免一些人為的資源浪費(fèi),后面的章節(jié)就從這點(diǎn)入手,講解App如何避免一些不必要的消耗。
6)Overdraw問題
Overdraw(過度繪制)描述的是屏幕上的某個(gè)像素在同一幀的時(shí)間內(nèi)被繪制了多次。在多層次重疊的UI結(jié)構(gòu)里面,如果不可見的UI也在做繪制的操作,會(huì)導(dǎo)致某些像素區(qū)域被繪制了多次。這樣就會(huì)浪費(fèi)大量的CPU以及GPU資源。
當(dāng)設(shè)計(jì)上追求更華麗的視覺效果的時(shí)候,我們就容易陷入采用復(fù)雜的多層次重疊視圖來實(shí)現(xiàn)這種視覺效果的怪圈。這很容易導(dǎo)致大量的性能問題,為了獲得最佳的性能,我們必須盡量減少Overdraw的情況發(fā)生。
幸運(yùn)的是,我們可以通過手機(jī)設(shè)置里面的開發(fā)者選項(xiàng),打開Show GPU Overdraw的選項(xiàng),觀察UI上的Overdraw情況。
藍(lán)色,淡綠,淡紅,深紅代表了4種不同程度的Overdraw情況,我們的目標(biāo)就是盡量減少紅色Overdraw,看到更多的藍(lán)色區(qū)域。
7)Visualize and Fix Overdraw - Quiz & Solution
這里舉了一個(gè)例子,通過XML文件可以看到有好幾處非必需的background。通過把XML中非必需的background移除之后,可以顯著減少布局的過度繪制。其中一個(gè)比較有意思的地方是:針對ListView中的Avatar ImageView的設(shè)置,在getView的代碼里面,判斷是否獲取到對應(yīng)的Bitmap,在獲取到Avatar的圖像之后,把ImageView的Background設(shè)置為Transparent,只有當(dāng)圖像沒有獲取到的時(shí)候才設(shè)置對應(yīng)的Background占位圖片,這樣可以避免因?yàn)榻oAvatar設(shè)置背景圖而導(dǎo)致的過度渲染。
總結(jié)一下,優(yōu)化步驟如下:
移除Window默認(rèn)的Background
移除XML布局文件中非必需的Background
按需顯示占位背景圖片
8)ClipRect & QuickReject
前面有提到過,對不可見的UI組件進(jìn)行繪制更新會(huì)導(dǎo)致Overdraw。例如Nav Drawer從前置可見的Activity滑出之后,如果還繼續(xù)繪制那些在Nav Drawer里面不可見的UI組件,這就導(dǎo)致了Overdraw。為了解決這個(gè)問題,Android系統(tǒng)會(huì)通過避免繪制那些完全不可見的組件來盡量減少Overdraw。那些Nav Drawer里面不可見的View就不會(huì)被執(zhí)行浪費(fèi)資源。
但是不幸的是,對于那些過于復(fù)雜的自定義的View(通常重寫了onDraw方法),Android系統(tǒng)無法檢測在onDraw里面具體會(huì)執(zhí)行什么操作,系統(tǒng)無法監(jiān)控并自動(dòng)優(yōu)化,也就無法避免Overdraw了。但是我們可以通過canvas.clipRect()來幫助系統(tǒng)識(shí)別那些可見的區(qū)域。這個(gè)方法可以指定一塊矩形區(qū)域,只有在這個(gè)區(qū)域內(nèi)才會(huì)被繪制,其他的區(qū)域會(huì)被忽視。這個(gè)API可以很好的幫助那些有多組重疊組件的自定義View來控制顯示的區(qū)域。同時(shí)clipRect方法還可以幫助節(jié)約CPU與GPU資源,在clipRect區(qū)域之外的繪制指令都不會(huì)被執(zhí)行,那些部分內(nèi)容在矩形區(qū)域內(nèi)的組件,仍然會(huì)得到繪制。
除了clipRect方法之外,我們還可以使用canvas.quickreject()來判斷是否沒和某個(gè)矩形相交,從而跳過那些非矩形區(qū)域內(nèi)的繪制操作。
9)Apply clipRect and quickReject - Quiz & Solution
上面的示例圖中顯示了一個(gè)自定義的View,主要效果是呈現(xiàn)多張重疊的卡片。這個(gè)View的onDraw方法如下圖所示:
打開開發(fā)者選項(xiàng)中的顯示過度渲染,可以看到我們這個(gè)自定義的View部分區(qū)域存在著過度繪制。那么是什么原因?qū)е逻^度繪制的呢?
10)Fixing Overdraw with Canvas API
下面的代碼顯示了如何通過clipRect來解決自定義View的過度繪制,提高自定義View的繪制性能:
下面是優(yōu)化過后的效果:
11)Layouts, Invalidations and Perf
Android需要把XML布局文件轉(zhuǎn)換成GPU能夠識(shí)別并繪制的對象。這個(gè)操作是在DisplayList的幫助下完成的。DisplayList持有所有將要交給GPU繪制到屏幕上的數(shù)據(jù)信息。
在某個(gè)View第一次需要被渲染時(shí),Display List會(huì)因此被創(chuàng)建,當(dāng)這個(gè)View要顯示到屏幕上時(shí),我們會(huì)執(zhí)行GPU的繪制指令來進(jìn)行渲染。
如果View的Property屬性發(fā)生了改變(例如移動(dòng)位置),我們就僅僅需要Execute Display List就夠了。
然而如果你修改了View中的某些可見組件的內(nèi)容,那么之前的DisplayList就無法繼續(xù)使用了,我們需要重新創(chuàng)建一個(gè)DisplayList并重新執(zhí)行渲染指令更新到屏幕上。
請注意:任何時(shí)候View中的繪制內(nèi)容發(fā)生變化時(shí),都會(huì)需要重新創(chuàng)建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。這個(gè)流程的表現(xiàn)性能取決于你的View的復(fù)雜程度,View的狀態(tài)變化以及渲染管道的執(zhí)行性能。舉個(gè)例子,假設(shè)某個(gè)Button的大小需要增大到目前的兩倍,在增大Button大小之前,需要通過父View重新計(jì)算并擺放其他子View的位置。修改View的大小會(huì)觸發(fā)整個(gè)HierarcyView的重新計(jì)算大小的操作。如果是修改View的位置則會(huì)觸發(fā)HierarchView重新計(jì)算其他View的位置。如果布局很復(fù)雜,這就會(huì)很容易導(dǎo)致嚴(yán)重的性能問題。
12)Hierarchy Viewer: Walkthrough
Hierarchy Viewer可以很直接的呈現(xiàn)布局的層次關(guān)系,視圖組件的各種屬性。 我們可以通過紅,黃,綠三種不同的顏色來區(qū)分布局的Measure,Layout,Executive的相對性能表現(xiàn)如何。
13)Nested Hierarchies and Performance
提升布局性能的關(guān)鍵點(diǎn)是盡量保持布局層級(jí)的扁平化,避免出現(xiàn)重復(fù)的嵌套布局。例如下面的例子,有2行顯示相同內(nèi)容的視圖,分別用兩種不同的寫法來實(shí)現(xiàn),他們有著不同的層級(jí)。
下圖顯示了使用2種不同的寫法,在Hierarchy Viewer上呈現(xiàn)出來的性能測試差異:
14)Optimizing Your Layout
下圖舉例演示了如何優(yōu)化ListItem的布局,通過RelativeLayout替代舊方案中的嵌套LinearLayout來優(yōu)化布局。