Android性能優(yōu)化之渲染篇

總結(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)基本概念

在一個典型的顯示系統(tǒng)中,一般包括CPU、GPU、display三個部分, CPU負(fù)責(zé)計計算數(shù)據(jù),把計算好數(shù)據(jù)交給GPU,GPU會對圖形數(shù)據(jù)進(jìn)行渲染,渲染好后放到buffer里存起來,然后display(有的文章也叫屏幕或者顯示器)負(fù)責(zé)把buffer里的數(shù)據(jù)呈現(xiàn)到屏幕上。很多時候,我們可以把CPU、GPU放在一起說,那么就是包括2部分,CPU/GPU 和display(本文主要按后面這種分類來解釋)。
tearing: 一個屏幕內(nèi)的數(shù)據(jù)來自2個不同的幀,畫面會出現(xiàn)撕裂感。
jank: 一個幀在屏幕上連續(xù)出現(xiàn)2次。
lag:從用戶體驗來說,就是點擊下去到呈現(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ù),然后顯示出來。如果只有一個buffer,那么這個buffer既被GPU寫,也同時被display讀,如果讀的速度跟寫的速度一樣,那可以正常顯示。如果讀的比寫的快(顯示器刷新頻率略快于CPU/GPU準(zhǔn)備緩存的速度),那也沒什么問題。但是如果讀的比寫的慢的話,很可能有buffer里的數(shù)據(jù)沒有被讀取,就被重寫了,這樣相當(dāng)于一部分?jǐn)?shù)據(jù)丟失了,這是不允許的。比如display在讀取幀1的過程中(為了顯示幀1),CPU/GPU把幀2寫到了buffer里 ,而display并不知道此時buffer里已經(jīng)是幀2了,那么就會出現(xiàn)display讀的上半部分是幀1,下半部分是2的, 出現(xiàn)畫面“割裂”,這就叫tearing。

double-buffer

tearing發(fā)生的原因是display讀buffer時,buffer被修改,那么多一個buffer是不是能解決問題,是的,事實上目前所有的顯示系統(tǒng)都是雙緩存的,單緩存存在于30年前。
雙緩沖技術(shù),基本原理就是采用兩塊buffer。一塊back buffer用于CPU/GPU后臺繪制,另一塊framebuffer則用于顯示,當(dāng)back buffer準(zhǔn)備就緒后,它們才進(jìn)行交換。不可否認(rèn),doublebuffering可以在很大程度上降低screen tearing錯誤,但是它是萬能的嗎?

一個需要考慮的問題是我們什么時候進(jìn)行兩個緩沖區(qū)的交換呢?假如是back buffer準(zhǔn)備完成一幀數(shù)據(jù)以后就進(jìn)行,那么如果此時屏幕還沒有完整顯示上一幀內(nèi)容的話,肯定是會出問題的。看來只能是等到屏幕處理完一幀數(shù)據(jù)后,才可以執(zhí)行這一操作了。

我們知道,一個典型的顯示器有兩個重要特性,行頻和場頻。行頻(HorizontalScanningFrequency)又稱為“水平掃描頻率”,是屏幕每秒鐘從左至右掃描的次數(shù); 場頻(Vertical Scanning Frequency)也稱為“垂直掃描頻率”,是每秒鐘整個屏幕刷新的次數(shù)。由此也可以得出它們的關(guān)系:行頻=場頻*縱坐標(biāo)分辨率。

當(dāng)掃描完一個屏幕后,設(shè)備需要重新回到第一行以進(jìn)入下一次的循環(huán),此時有一段時間空隙,稱為VerticalBlanking Interval(VBI)。大家應(yīng)該能想到了,這個時間點就是我們進(jìn)行緩沖區(qū)交換的最佳時間。因為此時屏幕沒有在刷新,也就避免了交換過程中出現(xiàn)screentearing的狀況。VSync(垂直同步)是VerticalSynchronization的簡寫,它利用VBI時期出現(xiàn)的vertical sync pulse來保證雙緩沖在最佳時間點才進(jìn)行交換。

總結(jié)

Screen Tearing出現(xiàn)的原因有兩個:
1,單緩沖情況下,在display的時候draw(也就是所謂的On Display Draw)
2,雙緩沖情況下,在display的時候swap buffer(Flip).
所以:
我們只要使用雙緩沖做Flip(避免了On display Draw),并且做VSync同步,即每次等到VSync階段再做swap,就可以完美解決Screen Tearing問題。

3)VSYNC的前生今世

VSYNC(Vertical Synchronization)是一個相當(dāng)古老的概念,對于游戲玩家,它有一個更加大名鼎鼎的中文名字—-垂直同步。“垂直同步(vsync)”指的是顯卡的輸出幀數(shù)和屏幕的垂直刷新率相同,這完全是一個CRT顯示器上的概念。其實無論是VSYNC還是垂直同步這個名字,因為LCD根本就沒有垂直掃描的這種東西,因此這個名字本身已經(jīng)沒有意義。但是基于歷史的原因,這個名稱在圖形圖像領(lǐng)域被沿襲下來。在當(dāng)下,垂直同步的含義我們可以理解為,使得顯卡生成幀的速度和屏幕刷新的速度的保持一致。舉例來說,如果屏幕的刷新率為60Hz,那么生成幀的速度就應(yīng)該被固定在1/60 s。

Android中的VSYNC — 黃油計劃

從Android 4.1開始,谷歌致力于解決Android系統(tǒng)中最飽受詬病的一個問題,滑動不如iOS流暢。因谷歌在4.1版本引入了一個重大的改進(jìn)—Project Butter,也即是黃油計劃。Project Butter對Android Display系統(tǒng)進(jìn)行了重構(gòu),引入了三個核心元素,即VSYNC、Triple Buffer和Choreographer。關(guān)于后面兩個概念我們會在后面開專題講解,這里我們重點講解VSYNC的作用。玩過大型PC游戲的玩家都知道,VSYNC最重要的作用是防止出現(xiàn)畫面撕裂(screentearing)。所謂畫面撕裂,就是指一個畫面上出現(xiàn)了兩幀畫面的內(nèi)容,如下圖。


為什么會出現(xiàn)這種情況呢?這種情況一般是因為顯卡輸出幀的速度高于顯示器的刷新速度,導(dǎo)致顯示器并不能及時處理輸出的幀,而最終出現(xiàn)了多個幀的畫面都留在了顯示器上的問題。這也就是我們所說的畫面撕裂。

提到垂直同步這里就多提一句,其實我認(rèn)為對于PC上的大型游戲來說,只有配置足夠高,高到顯卡輸出幀率可以穩(wěn)定的高于顯示器的刷新頻率,才有開啟垂直同步的必要。因為只有這個時候,畫面撕裂才會真正成為一個問題。而對于很多情況下主機(jī)性能不足導(dǎo)致游戲輸出幀率低于顯示器的刷新頻率的情況下,尤其是幀率穩(wěn)定在40~60之間時,開啟垂直同步可能會導(dǎo)致幀率倍數(shù)級的下降(具體原因我們在Graphic架構(gòu)一文中提到過,當(dāng)幀生成速度不及VSync速度時,幀率的下降不是平緩的,而且很可能是倍數(shù)級的。當(dāng)然這在android系統(tǒng)上并非嚴(yán)重問題,因為android上很少有高速的復(fù)雜場景的頻繁切換。事實上,在Android的普通應(yīng)用場景下,VSync的使用不僅不會降低幀率,還可以有效解決卡頓問題)。

4)Jank問題,triple buffer

如果沒有VSync同步, 繪圖速度大于顯示速度那么會有問題(screen tearing).
如果沒有VSync同步,還有另一個問題,就是反過來繪圖速度過慢的時候.
如圖::


這個圖中有三個元素,Display是顯示屏幕,GPU和CPU負(fù)責(zé)渲染幀數(shù)據(jù),每個幀以方框表示,并以數(shù)字進(jìn)行編號,如0、1、2等等。VSync用于指導(dǎo)雙緩沖區(qū)的交換。以時間的順序來看下將會發(fā)生的異常:
Step1. Display顯示第0幀數(shù)據(jù),此時CPU和GPU渲染第1幀畫面,而且趕在Display顯示下一幀前完成。
Step2. 因為渲染及時,Display在第0幀顯示完成后,也就是第1個VSync后,正常顯示第1幀。
Step3. 由于某些原因,比如CPU資源被占用,系統(tǒng)沒有及時地開始處理第2幀,直到第2個VSync快來前才開始處理
Step4. 第2個VSync來時,由于第2幀數(shù)據(jù)還沒有準(zhǔn)備就緒,顯示的還是第1幀。這種情況被Android開發(fā)組命名為“Jank”。
Step5. 當(dāng)?shù)?幀數(shù)據(jù)準(zhǔn)備完成后,它并不會馬上被顯示,而是要等待下一個VSync。所以總的來說,就是屏幕平白無故地多顯示了一次第1幀。原因大家應(yīng)該都看到了,就是CPU沒有及時地開始著手處理第2幀的渲染工作,以致“延誤軍機(jī)”。

其實總結(jié)上面的這個情況之所以發(fā)生,首先的原因就在于第二幀沒有及時的繪制。那么如何使得第二幀及時被繪制呢?這就是我們在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)備時間。
假如CPU/GPU的FPS(FramesPer Second)高于這個值,那么這個方案是完美的,顯示效果將很好??墒俏覀儧]有辦法保證所有設(shè)備的硬件配置都能達(dá)到要求。假如CPU/GPU的性能無法滿足上圖的條件,又是什么情況呢?

在分析這一問題之前,我們先來看下正常情況下,采用雙緩沖區(qū)的系統(tǒng)的運行情況,如圖:

這個圖采用了雙緩沖,以及前面介紹的VSync,可以看到整個過程還是相當(dāng)不錯的,雖然CPU/GPU處理所用的時間時短時長,但總的來說都在16ms以內(nèi),因而不影響顯示效果。A和B分別代表兩個緩沖區(qū),它們不斷地交換來正確顯示畫面。

現(xiàn)在我們可以繼續(xù)分析FPS低于屏幕刷新率的情況,如圖所示:

當(dāng)CPU/GPU的處理時間超過16ms時,第一個VSync到來時,緩沖區(qū)B中的數(shù)據(jù)還沒有準(zhǔn)備好,于是只能繼續(xù)顯示之前A緩沖區(qū)中的內(nèi)容。而B完成后,又因為缺乏VSync pulse信號,它只能等待下一個signal的來臨。于是在這一過程中,有一大段時間是被浪費的。當(dāng)下一個VSync出現(xiàn)時,CPU/GPU馬上執(zhí)行操作,此時它可操作的buffer是A,相應(yīng)的顯示屏對應(yīng)的就是B。這時看起來就是正常的。只不過由于執(zhí)行時間仍然超過16ms,導(dǎo)致下一次應(yīng)該執(zhí)行的緩沖區(qū)交換又被推遲了——如此循環(huán)反復(fù),便出現(xiàn)了越來越多的“Jank”。
那么有沒有規(guī)避的辦法呢?

很顯然,第一次的Jank看起來是沒有辦法的,除非升級硬件配置來加快FPS。我們關(guān)注的重點是被CPU/GPU浪費的時間段,怎么才能充分利用起來呢?分析上述的過程,造成CPU/GPU無事可做的假象是因為當(dāng)前已經(jīng)沒有可用的buffer了。換句話說,如果增加一個buffer,情況會不會好轉(zhuǎn)呢?如圖:

Triple Buffering是MultipleBuffering的一種,指的是系統(tǒng)使用3個緩沖區(qū)用于顯示工作。我們來逐步分析下這個新機(jī)制是否有效。首先和預(yù)料中的一致,第一次“Jank”無可厚非。不過讓人欣慰的是,當(dāng)?shù)谝淮蜼Sync發(fā)生后,CPU不用再等待了,它會使用第三個buffer C來進(jìn)行下一幀數(shù)據(jù)的準(zhǔn)備工作。雖然對緩沖區(qū)C的處理所需時間同樣超過了16ms,但這并不影響顯示屏——第2次VSync到來后,它選擇buffer B進(jìn)行顯示;而第3次VSync時,它會接著采用C,而不是像double buffering中所看到的情況一樣只能再顯示一遍了。這樣子就有效地降低了系統(tǒng)顯示錯誤的機(jī)率。
注意:triple Bufferring好像完美解決了連續(xù)jank問題,但是第一次的jank無法避免,而且還有l(wèi)ag問題,例如緩存區(qū)C的內(nèi)容要16ms以后才能顯示。

5)用戶為什么會感覺到卡頓
根本原因

大多數(shù)用戶感知到的卡頓等性能問題的最主要根源都是因為渲染性能。從設(shè)計師的角度,他們希望App能夠有更多的動畫,圖片等時尚元素來實現(xiàn)流暢的用戶體驗。但是Android系統(tǒng)很有可能無法及時完成那些復(fù)雜的界面渲染操作。Android系統(tǒng)每隔16ms發(fā)出VSYNC信號,觸發(fā)對UI進(jìn)行渲染,如果每次渲染都成功,這樣就能夠達(dá)到流暢的畫面所需要的60fps,為了能夠?qū)崿F(xiàn)60fps,這意味著程序的大多數(shù)操作都必須在16ms內(nèi)完成。



如果你的某個操作花費時間是24ms,系統(tǒng)在得到VSYNC信號的時候就無法進(jìn)行正常渲染,這樣就發(fā)生了丟幀現(xiàn)象。那么用戶在32ms內(nèi)看到的會是同一幀畫面。



用戶容易在UI執(zhí)行動畫或者滑動ListView的時候感知到卡頓不流暢,是因為這里的操作相對復(fù)雜,容易發(fā)生丟幀的現(xiàn)象,從而感覺卡頓。有很多原因可以導(dǎo)致丟幀,也許是因為你的layout太過復(fù)雜,無法在16ms內(nèi)完成渲染,有可能是因為你的UI上有層疊太多的繪制單元,還有可能是因為動畫執(zhí)行的次數(shù)過多。這些都會導(dǎo)致CPU或者GPU負(fù)載過重。
渲染過程CPU和GPU的分工

渲染操作通常依賴于兩個核心組件:CPU與GPU。CPU負(fù)責(zé)包括Measure,Layout,Record,Execute的計算操作,GPU負(fù)責(zé)Rasterization(柵格化)操作。CPU通常存在的問題的原因是存在非必需的視圖組件,它不僅僅會帶來重復(fù)的計算操作,而且還會占用額外的GPU資源。


了解Android是如何利用GPU進(jìn)行畫面渲染有助于我們更好的理解性能問題。一個很直接的問題是:activity的畫面是如何繪制到屏幕上的?那些復(fù)雜的XML布局文件又是如何能夠被識別并繪制出來的?


Resterization柵格化是繪制那些Button,Shape,Path,String,Bitmap等組件最基礎(chǔ)的操作。它把那些組件拆分到不同的像素上進(jìn)行顯示。這是一個很費時的操作,GPU的引入就是為了加快柵格化的操作。
CPU負(fù)責(zé)把UI組件計算成Polygons,Texture紋理,然后交給GPU進(jìn)行柵格化渲染。

然而每次從CPU轉(zhuǎn)移到GPU是一件很麻煩的事情,所幸的是OpenGL ES可以把那些需要渲染的紋理Hold在GPU Memory里面,在下次需要渲染的時候直接進(jìn)行操作。所以如果你更新了GPU所hold住的紋理內(nèi)容,那么之前保存的狀態(tài)就丟失了。
在Android里面那些由主題所提供的資源,例如Bitmaps,Drawables都是一起打包到統(tǒng)一的Texture紋理當(dāng)中,然后再傳遞到GPU里面,這意味著每次你需要使用這些資源的時候,都是直接從紋理里面進(jìn)行獲取渲染的。當(dāng)然隨著UI組件的越來越豐富,有了更多演變的形態(tài)。例如顯示圖片的時候,需要先經(jīng)過CPU的計算加載到內(nèi)存中,然后傳遞給GPU進(jìn)行渲染。文字的顯示比較復(fù)雜,需要先經(jīng)過CPU換算成紋理,然后交給GPU進(jìn)行渲染,返回到CPU繪制單個字符的時候,再重新引用經(jīng)過GPU渲染的內(nèi)容。動畫則存在一個更加復(fù)雜的操作流程。

為了能夠使得App流暢,我們需要在每幀16ms以內(nèi)處理完所有的CPU與GPU的計算,繪制,渲染等等操作。

后面章節(jié)引言:

前幾章講解的很多display相關(guān)的概念,也描述了Android一些機(jī)制來盡量保證顯示流暢。
double buffering 和VSync解決了 screen tearing 問題。
Triple buffering解決了連續(xù)jank問題,但是第一次的jank還是無法避免,而且引入了lag問題,用戶還是會感覺到卡頓。
假設(shè)硬件不升級的情況下,如果我們系統(tǒng)能在16ms之內(nèi)完成所有渲染工作,那這些問題都從根本上解決(理想很豐滿,現(xiàn)實很骨感)。我們APP編寫者還是要節(jié)約資源,避免一些人為的資源浪費,后面的章節(jié)就從這點入手,講解App如何避免一些不必要的消耗。

6)Overdraw問題

Overdraw(過度繪制)描述的是屏幕上的某個像素在同一幀的時間內(nèi)被繪制了多次。在多層次重疊的UI結(jié)構(gòu)里面,如果不可見的UI也在做繪制的操作,會導(dǎo)致某些像素區(qū)域被繪制了多次。這樣就會浪費大量的CPU以及GPU資源。



當(dāng)設(shè)計上追求更華麗的視覺效果的時候,我們就容易陷入采用復(fù)雜的多層次重疊視圖來實現(xiàn)這種視覺效果的怪圈。這很容易導(dǎo)致大量的性能問題,為了獲得最佳的性能,我們必須盡量減少Overdraw的情況發(fā)生。
幸運的是,我們可以通過手機(jī)設(shè)置里面的開發(fā)者選項,打開Show GPU Overdraw的選項,觀察UI上的Overdraw情況。



藍(lán)色,淡綠,淡紅,深紅代表了4種不同程度的Overdraw情況,我們的目標(biāo)就是盡量減少紅色Overdraw,看到更多的藍(lán)色區(qū)域。
7)Visualize and Fix Overdraw - Quiz & Solution

這里舉了一個例子,通過XML文件可以看到有好幾處非必需的background。通過把XML中非必需的background移除之后,可以顯著減少布局的過度繪制。其中一個比較有意思的地方是:針對ListView中的Avatar ImageView的設(shè)置,在getView的代碼里面,判斷是否獲取到對應(yīng)的Bitmap,在獲取到Avatar的圖像之后,把ImageView的Background設(shè)置為Transparent,只有當(dāng)圖像沒有獲取到的時候才設(shè)置對應(yīng)的Background占位圖片,這樣可以避免因為給Avatar設(shè)置背景圖而導(dǎo)致的過度渲染。



總結(jié)一下,優(yōu)化步驟如下:
移除Window默認(rèn)的Background
移除XML布局文件中非必需的Background
按需顯示占位背景圖片

8)ClipRect & QuickReject

前面有提到過,對不可見的UI組件進(jìn)行繪制更新會導(dǎo)致Overdraw。例如Nav Drawer從前置可見的Activity滑出之后,如果還繼續(xù)繪制那些在Nav Drawer里面不可見的UI組件,這就導(dǎo)致了Overdraw。為了解決這個問題,Android系統(tǒng)會通過避免繪制那些完全不可見的組件來盡量減少Overdraw。那些Nav Drawer里面不可見的View就不會被執(zhí)行浪費資源。


但是不幸的是,對于那些過于復(fù)雜的自定義的View(通常重寫了onDraw方法),Android系統(tǒng)無法檢測在onDraw里面具體會執(zhí)行什么操作,系統(tǒng)無法監(jiān)控并自動優(yōu)化,也就無法避免Overdraw了。但是我們可以通過canvas.clipRect()來幫助系統(tǒng)識別那些可見的區(qū)域。這個方法可以指定一塊矩形區(qū)域,只有在這個區(qū)域內(nèi)才會被繪制,其他的區(qū)域會被忽視。這個API可以很好的幫助那些有多組重疊組件的自定義View來控制顯示的區(qū)域。同時clipRect方法還可以幫助節(jié)約CPU與GPU資源,在clipRect區(qū)域之外的繪制指令都不會被執(zhí)行,那些部分內(nèi)容在矩形區(qū)域內(nèi)的組件,仍然會得到繪制。

除了clipRect方法之外,我們還可以使用canvas.quickreject()來判斷是否沒和某個矩形相交,從而跳過那些非矩形區(qū)域內(nèi)的繪制操作。

9)Apply clipRect and quickReject - Quiz & Solution

上面的示例圖中顯示了一個自定義的View,主要效果是呈現(xiàn)多張重疊的卡片。這個View的onDraw方法如下圖所示:



打開開發(fā)者選項中的顯示過度渲染,可以看到我們這個自定義的View部分區(qū)域存在著過度繪制。那么是什么原因?qū)е逻^度繪制的呢?


10)Fixing Overdraw with Canvas API

下面的代碼顯示了如何通過clipRect來解決自定義View的過度繪制,提高自定義View的繪制性能:



下面是優(yōu)化過后的效果:


11)Layouts, Invalidations and Perf

Android需要把XML布局文件轉(zhuǎn)換成GPU能夠識別并繪制的對象。這個操作是在DisplayList的幫助下完成的。DisplayList持有所有將要交給GPU繪制到屏幕上的數(shù)據(jù)信息。
在某個View第一次需要被渲染時,Display List會因此被創(chuàng)建,當(dāng)這個View要顯示到屏幕上時,我們會執(zhí)行GPU的繪制指令來進(jìn)行渲染。
如果View的Property屬性發(fā)生了改變(例如移動位置),我們就僅僅需要Execute Display List就夠了。


然而如果你修改了View中的某些可見組件的內(nèi)容,那么之前的DisplayList就無法繼續(xù)使用了,我們需要重新創(chuàng)建一個DisplayList并重新執(zhí)行渲染指令更新到屏幕上。

請注意:任何時候View中的繪制內(nèi)容發(fā)生變化時,都會需要重新創(chuàng)建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。這個流程的表現(xiàn)性能取決于你的View的復(fù)雜程度,View的狀態(tài)變化以及渲染管道的執(zhí)行性能。舉個例子,假設(shè)某個Button的大小需要增大到目前的兩倍,在增大Button大小之前,需要通過父View重新計算并擺放其他子View的位置。修改View的大小會觸發(fā)整個HierarcyView的重新計算大小的操作。如果是修改View的位置則會觸發(fā)HierarchView重新計算其他View的位置。如果布局很復(fù)雜,這就會很容易導(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)鍵點是盡量保持布局層級的扁平化,避免出現(xiàn)重復(fù)的嵌套布局。例如下面的例子,有2行顯示相同內(nèi)容的視圖,分別用兩種不同的寫法來實現(xiàn),他們有著不同的層級。



image

下圖顯示了使用2種不同的寫法,在Hierarchy Viewer上呈現(xiàn)出來的性能測試差異:


14)Optimizing Your Layout

下圖舉例演示了如何優(yōu)化ListItem的布局,通過RelativeLayout替代舊方案中的嵌套LinearLayout來優(yōu)化布局。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容