Android性能優(yōu)化典范

2015-03-04 09:47:45來(lái)源:胡凱的博客作者:

2015年伊始,Google發(fā)布了關(guān)于Android性能優(yōu)化典范的專(zhuān)題,一共16個(gè)短視頻,每個(gè)3-5分鐘,幫助開(kāi)發(fā)者創(chuàng)建更快更優(yōu)秀的Android App。課程專(zhuān)題不僅僅介紹了Android系統(tǒng)中有關(guān)性能問(wèn)題的底層工作原理,同時(shí)也介紹了如何通過(guò)工具來(lái)找出性能問(wèn)題以及提升性能的建議。

2015年伊始,Google發(fā)布了關(guān)于Android性能優(yōu)化典范的專(zhuān)題,一共16個(gè)短視頻,每個(gè)3-5分鐘,幫助開(kāi)發(fā)者創(chuàng)建更快更優(yōu)秀的Android App。課程專(zhuān)題不僅僅介紹了Android系統(tǒng)中有關(guān)性能問(wèn)題的底層工作原理,同時(shí)也介紹了如何通過(guò)工具來(lái)找出性能問(wèn)題以及提升性能的建議。主要從三個(gè)方面展開(kāi),Android的渲染機(jī)制,內(nèi)存與GC,電量?jī)?yōu)化。下面是對(duì)這些問(wèn)題和建議的總結(jié)梳理。

0)Render Performance

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

\

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

\

用戶容易在UI執(zhí)行動(dòng)畫(huà)或者滑動(dòng)ListView的時(shí)候感知到卡頓不流暢,是因?yàn)檫@里的操作相對(duì)復(fù)雜,容易發(fā)生丟幀的現(xiàn)象,從而感覺(jué)卡頓。有很多原因可以導(dǎo)致丟幀,也許是因?yàn)槟愕膌ayout太過(guò)復(fù)雜,無(wú)法在16ms內(nèi)完成渲染,有可能是因?yàn)槟愕腢I上有層疊太多的繪制單元,還有可能是因?yàn)閯?dòng)畫(huà)執(zhí)行的次數(shù)過(guò)多。這些都會(huì)導(dǎo)致CPU或者GPU負(fù)載過(guò)重。

我們可以通過(guò)一些工具來(lái)定位問(wèn)題,比如可以使用HierarchyViewer來(lái)查找Activity中的布局是否過(guò)于復(fù)雜,也可以使用手機(jī)設(shè)置里面的開(kāi)發(fā)者選項(xiàng),打開(kāi)Show GPU Overdraw等選項(xiàng)進(jìn)行觀察。你還可以使用TraceView來(lái)觀察CPU的執(zhí)行情況,更加快捷的找到性能瓶頸。

1)Understanding Overdraw

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

\

當(dāng)設(shè)計(jì)上追求更華麗的視覺(jué)效果的時(shí)候,我們就容易陷入采用越來(lái)越多的層疊組件來(lái)實(shí)現(xiàn)這種視覺(jué)效果的怪圈。這很容易導(dǎo)致大量的性能問(wèn)題,為了獲得最佳的性能,我們必須盡量減少Overdraw的情況發(fā)生。

幸運(yùn)的是,我們可以通過(guò)手機(jī)設(shè)置里面的開(kāi)發(fā)者選項(xiàng),打開(kāi)Show GPU Overdraw的選項(xiàng),可以觀察UI上的Overdraw情況。

\

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

Overdraw有時(shí)候是因?yàn)槟愕腢I布局存在大量重疊的部分,還有的時(shí)候是因?yàn)榉潜仨毜闹丿B背景。例如某個(gè)Activity有一個(gè)背景,然后里面的Layout又有自己的背景,同時(shí)子View又分別有自己的背景。僅僅是通過(guò)移除非必須的背景圖片,這就能夠減少大量的紅色Overdraw區(qū)域,增加藍(lán)色區(qū)域的占比。這一措施能夠顯著提升程序性能。

2)Understanding VSYNC

為了理解App是如何進(jìn)行渲染的,我們必須了解手機(jī)硬件是如何工作,那么就必須理解什么是VSYNC。

在講解VSYNC之前,我們需要了解兩個(gè)相關(guān)的概念:

  • Refresh Rate:代表了屏幕在一秒內(nèi)刷新屏幕的次數(shù),這取決于硬件的固定參數(shù),例如60Hz。
  • Frame Rate:代表了GPU在一秒內(nèi)繪制操作的幀數(shù),例如30fps,60fps。

GPU會(huì)獲取圖形數(shù)據(jù)進(jìn)行渲染,然后硬件負(fù)責(zé)把渲染后的內(nèi)容呈現(xiàn)到屏幕上,他們兩者不停的進(jìn)行協(xié)作。

\

不幸的是,刷新頻率和幀率并不是總能夠保持相同的節(jié)奏。如果發(fā)生幀率與刷新頻率不一致的情況,就會(huì)容易出現(xiàn)Tearing的現(xiàn)象(畫(huà)面上下兩部分顯示內(nèi)容發(fā)生斷裂,來(lái)自不同的兩幀數(shù)據(jù)發(fā)生重疊)。

\

\

理解圖像渲染里面的雙重與三重緩存機(jī)制,這個(gè)概念比較復(fù)雜,請(qǐng)移步查看這里:http://source.android.com/devices/graphics/index.html,還有這里http://article.yeeyan.org/view/37503/304664

通常來(lái)說(shuō),幀率超過(guò)刷新頻率只是一種理想的狀況,在超過(guò)60fps的情況下,GPU所產(chǎn)生的幀數(shù)據(jù)會(huì)因?yàn)榈却齎SYNC的刷新信息而被Hold住,這樣能夠保持每次刷新都有實(shí)際的新的數(shù)據(jù)可以顯示。但是我們遇到更多的情況是幀率小于刷新頻率。

\

在這種情況下,某些幀顯示的畫(huà)面內(nèi)容就會(huì)與上一幀的畫(huà)面相同。糟糕的事情是,幀率從超過(guò)60fps突然掉到60fps以下,這樣就會(huì)發(fā)生LAG,JANK,HITCHING等卡頓掉幀的不順滑的情況。這也是用戶感受不好的原因所在。

3)Tool:Profile GPU Rendering

性能問(wèn)題如此的麻煩,幸好我們可以有工具來(lái)進(jìn)行調(diào)試。打開(kāi)手機(jī)里面的開(kāi)發(fā)者選項(xiàng),選擇Profile GPU Rendering,選中On screen as bars的選項(xiàng)。

\

選擇了這樣以后,我們可以在手機(jī)畫(huà)面上看到豐富的GPU繪制圖形信息,分別關(guān)于StatusBar,NavBar,激活的程序Activity區(qū)域的GPU Rending信息。

\

隨著界面的刷新,界面上會(huì)滾動(dòng)顯示垂直的柱狀圖來(lái)表示每幀畫(huà)面所需要渲染的時(shí)間,柱狀圖越高表示花費(fèi)的渲染時(shí)間越長(zhǎng)。

\

中間有一根綠色的橫線,代表16ms,我們需要確保每一幀花費(fèi)的總時(shí)間都低于這條橫線,這樣才能夠避免出現(xiàn)卡頓的問(wèn)題。

\

每一條柱狀線都包含三部分,藍(lán)色代表測(cè)量繪制Display List的時(shí)間,紅色代表OpenGL渲染Display List所需要的時(shí)間,黃色代表CPU等待GPU處理的時(shí)間。

4)Why 60fps?

我們通常都會(huì)提到60fps與16ms,可是知道為何會(huì)是以程序是否達(dá)到60fps來(lái)作為App性能的衡量標(biāo)準(zhǔn)嗎?這是因?yàn)槿搜叟c大腦之間的協(xié)作無(wú)法感知超過(guò)60fps的畫(huà)面更新。

12fps大概類(lèi)似手動(dòng)快速翻動(dòng)書(shū)籍的幀率,這明顯是可以感知到不夠順滑的。24fps使得人眼感知的是連續(xù)線性的運(yùn)動(dòng),這其實(shí)是歸功于運(yùn)動(dòng)模糊的效果。24fps是電影膠圈通常使用的幀率,因?yàn)檫@個(gè)幀率已經(jīng)足夠支撐大部分電影畫(huà)面需要表達(dá)的內(nèi)容,同時(shí)能夠最大的減少費(fèi)用支出。但是低于30fps是無(wú)法順暢表現(xiàn)絢麗的畫(huà)面內(nèi)容的,此時(shí)就需要用到60fps來(lái)達(dá)到想要的效果,當(dāng)然超過(guò)60fps是沒(méi)有必要的。

開(kāi)發(fā)app的性能目標(biāo)就是保持60fps,這意味著每一幀你只有16ms=1000/60的時(shí)間來(lái)處理所有的任務(wù)。

5)Android, UI and the GPU

了解Android是如何利用GPU進(jìn)行畫(huà)面渲染有助于我們更好的理解性能問(wèn)題。那么一個(gè)最實(shí)際的問(wèn)題是:activity的畫(huà)面是如何繪制到屏幕上的?那些復(fù)雜的XML布局文件又是如何能夠被識(shí)別并繪制出來(lái)的?

\

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組件的越來(lái)越豐富,有了更多演變的形態(tài)。例如顯示圖片的時(shí)候,需要先經(jīng)過(guò)CPU的計(jì)算加載到內(nèi)存中,然后傳遞給GPU進(jìn)行渲染。文字的顯示更加復(fù)雜,需要先經(jīng)過(guò)CPU換算成紋理,然后再交給GPU進(jìn)行渲染,回到CPU繪制單個(gè)字符的時(shí)候,再重新引用經(jīng)過(guò)GPU渲染的內(nèi)容。動(dòng)畫(huà)則是一個(gè)更加復(fù)雜的操作流程。

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

6)Invalidations, Layouts, and Performance

順滑精妙的動(dòng)畫(huà)是app設(shè)計(jì)里面最重要的元素之一,這些動(dòng)畫(huà)能夠顯著提升用戶體驗(yàn)。下面會(huì)講解Android系統(tǒng)是如何處理UI組件的更新操作的。

通常來(lái)說(shuō),Android需要把XML布局文件轉(zhuǎn)換成GPU能夠識(shí)別并繪制的對(duì)象。這個(gè)操作是在DisplayList的幫助下完成的。DisplayList持有所有將要交給GPU繪制到屏幕上的數(shù)據(jù)信息。

在某個(gè)View第一次需要被渲染時(shí),DisplayList會(huì)因此而被創(chuàng)建,當(dāng)這個(gè)View要顯示到屏幕上時(shí),我們會(huì)執(zhí)行GPU的繪制指令來(lái)進(jìn)行渲染。如果你在后續(xù)有執(zhí)行類(lèi)似移動(dòng)這個(gè)View的位置等操作而需要再次渲染這個(gè)View時(shí),我們就僅僅需要額外操作一次渲染指令就夠了。然而如果你修改了View中的某些可見(jiàn)組件,那么之前的DisplayList就無(wú)法繼續(xù)使用了,我們需要回頭重新創(chuàng)建一個(gè)DisplayList并且重新執(zhí)行渲染指令并更新到屏幕上。

需要注意的是:任何時(shí)候View中的繪制內(nèi)容發(fā)生變化時(shí),都會(huì)重新執(zhí)行創(chuàng)建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。這個(gè)流程的表現(xiàn)性能取決于你的View的復(fù)雜程度,View的狀態(tài)變化以及渲染管道的執(zhí)行性能。舉個(gè)例子,假設(shè)某個(gè)Button的大小需要增大到目前的兩倍,在增大Button大小之前,需要通過(guò)父View重新計(jì)算并擺放其他子View的位置。修改View的大小會(huì)觸發(fā)整個(gè)HierarcyView的重新計(jì)算大小的操作。如果是修改View的位置則會(huì)觸發(fā)HierarchView重新計(jì)算其他View的位置。如果布局很復(fù)雜,這就會(huì)很容易導(dǎo)致嚴(yán)重的性能問(wèn)題。我們需要盡量減少Overdraw。

\

我們可以通過(guò)前面介紹的Monitor GPU Rendering來(lái)查看渲染的表現(xiàn)性能如何,另外也可以通過(guò)開(kāi)發(fā)者選項(xiàng)里面的Show GPU view updates來(lái)查看視圖更新的操作,最后我們還可以通過(guò)HierarchyViewer這個(gè)工具來(lái)查看布局,使得布局盡量扁平化,移除非必需的UI組件,這些操作能夠減少M(fèi)easure,Layout的計(jì)算時(shí)間。

7)Overdraw, Cliprect, QuickReject

引起性能問(wèn)題的一個(gè)很重要的方面是因?yàn)檫^(guò)多復(fù)雜的繪制操作。我們可以通過(guò)工具來(lái)檢測(cè)并修復(fù)標(biāo)準(zhǔn)UI組件的Overdraw問(wèn)題,但是針對(duì)高度自定義的UI組件則顯得有些力不從心。

有一個(gè)竅門(mén)是我們可以通過(guò)執(zhí)行幾個(gè)APIs方法來(lái)顯著提升繪制操作的性能。前面有提到過(guò),非可見(jiàn)的UI組件進(jìn)行繪制更新會(huì)導(dǎo)致Overdraw。例如Nav Drawer從前置可見(jiàn)的Activity滑出之后,如果還繼續(xù)繪制那些在Nav Drawer里面不可見(jiàn)的UI組件,這就導(dǎo)致了Overdraw。為了解決這個(gè)問(wèn)題,Android系統(tǒng)會(huì)通過(guò)避免繪制那些完全不可見(jiàn)的組件來(lái)盡量減少Overdraw。那些Nav Drawer里面不可見(jiàn)的View就不會(huì)被執(zhí)行浪費(fèi)資源。

\

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

\

除了clipRect方法之外,我們還可以使用canvas.quickreject()來(lái)判斷是否沒(méi)和某個(gè)矩形相交,從而跳過(guò)那些非矩形區(qū)域內(nèi)的繪制操作。做了那些優(yōu)化之后,我們可以通過(guò)上面介紹的Show GPU Overdraw來(lái)查看效果。

8)Memory Churn and performance

雖然Android有自動(dòng)管理內(nèi)存的機(jī)制,但是對(duì)內(nèi)存的不恰當(dāng)使用仍然容易引起嚴(yán)重的性能問(wèn)題。在同一幀里面創(chuàng)建過(guò)多的對(duì)象是件需要特別引起注意的事情。

Android系統(tǒng)里面有一個(gè)Generational Heap Memory的模型,系統(tǒng)會(huì)根據(jù)內(nèi)存中不同的內(nèi)存數(shù)據(jù)類(lèi)型分別執(zhí)行不同的GC操作。例如,最近剛分配的對(duì)象會(huì)放在Young Generation區(qū)域,這個(gè)區(qū)域的對(duì)象通常都是會(huì)快速被創(chuàng)建并且很快被銷(xiāo)毀回收的,同時(shí)這個(gè)區(qū)域的GC操作速度也是比Old Generation區(qū)域的GC操作速度更快的。

\

除了速度差異之外,執(zhí)行GC操作的時(shí)候,任何線程的任何操作都會(huì)需要暫停,等待GC操作完成之后,其他操作才能夠繼續(xù)運(yùn)行。

\

通常來(lái)說(shuō),單個(gè)的GC并不會(huì)占用太多時(shí)間,但是大量不停的GC操作則會(huì)顯著占用幀間隔時(shí)間(16ms)。如果在幀間隔時(shí)間里面做了過(guò)多的GC操作,那么自然其他類(lèi)似計(jì)算,渲染等操作的可用時(shí)間就變得少了。

導(dǎo)致GC頻繁執(zhí)行有兩個(gè)原因:

  • Memory Churn內(nèi)存抖動(dòng),內(nèi)存抖動(dòng)是因?yàn)榇罅康膶?duì)象被創(chuàng)建又在短時(shí)間內(nèi)馬上被釋放。
  • 瞬間產(chǎn)生大量的對(duì)象會(huì)嚴(yán)重占用Young Generation的內(nèi)存區(qū)域,當(dāng)達(dá)到閥值,剩余空間不夠的時(shí)候,也會(huì)觸發(fā)GC。即使每次分配的對(duì)象占用了很少的內(nèi)存,但是他們疊加在一起會(huì)增加Heap的壓力,從而觸發(fā)更多其他類(lèi)型的GC。這個(gè)操作有可能會(huì)影響到幀率,并使得用戶感知到性能問(wèn)題。

\

解決上面的問(wèn)題有簡(jiǎn)潔直觀方法,如果你在Memory Monitor里面查看到短時(shí)間發(fā)生了多次內(nèi)存的漲跌,這意味著很有可能發(fā)生了內(nèi)存抖動(dòng)。

\

同時(shí)我們還可以通過(guò)Allocation Tracker來(lái)查看在短時(shí)間內(nèi),同一個(gè)棧中不斷進(jìn)出的相同對(duì)象。這是內(nèi)存抖動(dòng)的典型信號(hào)之一。

當(dāng)你大致定位問(wèn)題之后,接下去的問(wèn)題修復(fù)也就顯得相對(duì)直接簡(jiǎn)單了。例如,你需要避免在for循環(huán)里面分配對(duì)象占用內(nèi)存,需要嘗試把對(duì)象的創(chuàng)建移到循環(huán)體之外,自定義View中的onDraw方法也需要引起注意,每次屏幕發(fā)生繪制以及動(dòng)畫(huà)執(zhí)行過(guò)程中,onDraw方法都會(huì)被調(diào)用到,避免在onDraw方法里面執(zhí)行復(fù)雜的操作,避免創(chuàng)建對(duì)象。對(duì)于那些無(wú)法避免需要?jiǎng)?chuàng)建對(duì)象的情況,我們可以考慮對(duì)象池模型,通過(guò)對(duì)象池來(lái)解決頻繁創(chuàng)建與銷(xiāo)毀的問(wèn)題,但是這里需要注意結(jié)束使用之后,需要手動(dòng)釋放對(duì)象池中的對(duì)象。

9)Garbage Collection in Android

JVM的回收機(jī)制給開(kāi)發(fā)人員帶來(lái)很大的好處,不用時(shí)刻處理對(duì)象的分配與回收,可以更加專(zhuān)注于更加高級(jí)的代碼實(shí)現(xiàn)。相比起Java,C與C++等語(yǔ)言具備更高的執(zhí)行效率,他們需要開(kāi)發(fā)人員自己關(guān)注對(duì)象的分配與回收,但是在一個(gè)龐大的系統(tǒng)當(dāng)中,還是免不了經(jīng)常發(fā)生部分對(duì)象忘記回收的情況,這就是內(nèi)存泄漏。

原始JVM中的GC機(jī)制在Android中得到了很大程度上的優(yōu)化。Android里面是一個(gè)三級(jí)Generation的內(nèi)存模型,最近分配的對(duì)象會(huì)存放在Young Generation區(qū)域,當(dāng)這個(gè)對(duì)象在這個(gè)區(qū)域停留的時(shí)間達(dá)到一定程度,它會(huì)被移動(dòng)到Old Generation,最后到Permanent Generation區(qū)域。

\

每一個(gè)級(jí)別的內(nèi)存區(qū)域都有固定的大小,此后不斷有新的對(duì)象被分配到此區(qū)域,當(dāng)這些對(duì)象總的大小快達(dá)到這一級(jí)別內(nèi)存區(qū)域的閥值時(shí),會(huì)觸發(fā)GC的操作,以便騰出空間來(lái)存放其他新的對(duì)象。

\

前面提到過(guò)每次GC發(fā)生的時(shí)候,所有的線程都是暫停狀態(tài)的。GC所占用的時(shí)間和它是哪一個(gè)Generation也有關(guān)系,Young Generation的每次GC操作時(shí)間是最短的,Old Generation其次,Permanent Generation最長(zhǎng)。執(zhí)行時(shí)間的長(zhǎng)短也和當(dāng)前Generation中的對(duì)象數(shù)量有關(guān),遍歷查找20000個(gè)對(duì)象比起遍歷50個(gè)對(duì)象自然是要慢很多的。

雖然Google的工程師在盡量縮短每次GC所花費(fèi)的時(shí)間,但是特別注意GC引起的性能問(wèn)題還是很有必要。如果不小心在最小的for循環(huán)單元里面執(zhí)行了創(chuàng)建對(duì)象的操作,這將很容易引起GC并導(dǎo)致性能問(wèn)題。通過(guò)Memory Monitor我們可以查看到內(nèi)存的占用情況,每一次瞬間的內(nèi)存降低都是因?yàn)榇藭r(shí)發(fā)生了GC操作,如果在短時(shí)間內(nèi)發(fā)生大量的內(nèi)存上漲與降低的事件,這說(shuō)明很有可能這里有性能問(wèn)題。我們還可以通過(guò)Heap and Allocation Tracker工具來(lái)查看此時(shí)內(nèi)存中分配的到底有哪些對(duì)象。

10)Performance Cost of Memory Leaks

雖然Java有自動(dòng)回收的機(jī)制,可是這不意味著Java中不存在內(nèi)存泄漏的問(wèn)題,而內(nèi)存泄漏會(huì)很容易導(dǎo)致嚴(yán)重的性能問(wèn)題。

內(nèi)存泄漏指的是那些程序不再使用的對(duì)象無(wú)法被GC識(shí)別,這樣就導(dǎo)致這個(gè)對(duì)象一直留在內(nèi)存當(dāng)中,占用了寶貴的內(nèi)存空間。顯然,這還使得每級(jí)Generation的內(nèi)存區(qū)域可用空間變小,GC就會(huì)更容易被觸發(fā),從而引起性能問(wèn)題。

尋找內(nèi)存泄漏并修復(fù)這個(gè)漏洞是件很棘手的事情,你需要對(duì)執(zhí)行的代碼很熟悉,清楚的知道在特定環(huán)境下是如何運(yùn)行的,然后仔細(xì)排查。例如,你想知道程序中的某個(gè)activity退出的時(shí)候,它之前所占用的內(nèi)存是否有完整的釋放干凈了?首先你需要在activity處于前臺(tái)的時(shí)候使用Heap Tool獲取一份當(dāng)前狀態(tài)的內(nèi)存快照,然后你需要?jiǎng)?chuàng)建一個(gè)幾乎不這么占用內(nèi)存的空白activity用來(lái)給前一個(gè)Activity進(jìn)行跳轉(zhuǎn),其次在跳轉(zhuǎn)到這個(gè)空白的activity的時(shí)候主動(dòng)調(diào)用System.gc()方法來(lái)確保觸發(fā)一個(gè)GC操作。最后,如果前面這個(gè)activity的內(nèi)存都有全部正確釋放,那么在空白activity被啟動(dòng)之后的內(nèi)存快照中應(yīng)該不會(huì)有前面那個(gè)activity中的任何對(duì)象了。

\

如果你發(fā)現(xiàn)在空白activity的內(nèi)存快照中有一些可疑的沒(méi)有被釋放的對(duì)象存在,那么接下去就應(yīng)該使用Alocation Track Tool來(lái)仔細(xì)查找具體的可疑對(duì)象。我們可以從空白activity開(kāi)始監(jiān)聽(tīng),啟動(dòng)到觀察activity,然后再回到空白activity結(jié)束監(jiān)聽(tīng)。這樣操作以后,我們可以仔細(xì)觀察那些對(duì)象,找出內(nèi)存泄漏的真兇。

\

11)Memory Performance

通常來(lái)說(shuō),Android對(duì)GC做了大量的優(yōu)化操作,雖然執(zhí)行GC操作的時(shí)候會(huì)暫停其他任務(wù),可是大多數(shù)情況下,GC操作還是相對(duì)很安靜并且高效的。但是如果我們對(duì)內(nèi)存的使用不恰當(dāng),導(dǎo)致GC頻繁執(zhí)行,這樣就會(huì)引起不小的性能問(wèn)題。

為了尋找內(nèi)存的性能問(wèn)題,Android Studio提供了工具來(lái)幫助開(kāi)發(fā)者。

  • Memory Monitor:查看整個(gè)app所占用的內(nèi)存,以及發(fā)生GC的時(shí)刻,短時(shí)間內(nèi)發(fā)生大量的GC操作是一個(gè)危險(xiǎn)的信號(hào)。
  • Allocation Tracker:使用此工具來(lái)追蹤內(nèi)存的分配,前面有提到過(guò)。
  • Heap Tool:查看當(dāng)前內(nèi)存快照,便于對(duì)比分析哪些對(duì)象有可能是泄漏了的,請(qǐng)參考前面的Case。

12)Tool - Memory Monitor

Android Studio中的Memory Monitor可以很好的幫組我們查看程序的內(nèi)存使用情況。

\

\

\

13)Battery Performance

電量其實(shí)是目前手持設(shè)備最寶貴的資源之一,大多數(shù)設(shè)備都需要不斷的充電來(lái)維持繼續(xù)使用。不幸的是,對(duì)于開(kāi)發(fā)者來(lái)說(shuō),電量?jī)?yōu)化是他們最后才會(huì)考慮的的事情。但是可以確定的是,千萬(wàn)不能讓你的應(yīng)用成為消耗電量的大戶。

Purdue University研究了最受歡迎的一些應(yīng)用的電量消耗,平均只有30%左右的電量是被程序最核心的方法例如繪制圖片,擺放布局等等所使用掉的,剩下的70%左右的電量是被上報(bào)數(shù)據(jù),檢查位置信息,定時(shí)檢索后臺(tái)廣告信息所使用掉的。如何平衡這兩者的電量消耗,就顯得非常重要了。

有下面一些措施能夠顯著減少電量的消耗:

  • 我們應(yīng)該盡量減少喚醒屏幕的次數(shù)與持續(xù)的時(shí)間,使用WakeLock來(lái)處理喚醒的問(wèn)題,能夠正確執(zhí)行喚醒操作并根據(jù)設(shè)定及時(shí)關(guān)閉操作進(jìn)入睡眠狀態(tài)。
  • 某些非必須馬上執(zhí)行的操作,例如上傳歌曲,圖片處理等,可以等到設(shè)備處于充電狀態(tài)或者電量充足的時(shí)候才進(jìn)行。
  • 觸發(fā)網(wǎng)絡(luò)請(qǐng)求的操作,每次都會(huì)保持無(wú)線信號(hào)持續(xù)一段時(shí)間,我們可以把零散的網(wǎng)絡(luò)請(qǐng)求打包進(jìn)行一次操作,避免過(guò)多的無(wú)線信號(hào)引起的電量消耗。關(guān)于網(wǎng)絡(luò)請(qǐng)求引起無(wú)線信號(hào)的電量消耗,還可以參考這里http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html

我們可以通過(guò)手機(jī)設(shè)置選項(xiàng)找到對(duì)應(yīng)App的電量消耗統(tǒng)計(jì)數(shù)據(jù)。我們還可以通過(guò)Battery Historian Tool來(lái)查看詳細(xì)的電量消耗。

\

如果發(fā)現(xiàn)我們的App有電量消耗過(guò)多的問(wèn)題,我們可以使用JobScheduler API來(lái)對(duì)一些任務(wù)進(jìn)行定時(shí)處理,例如我們可以把那些任務(wù)重的操作等到手機(jī)處于充電狀態(tài),或者是連接到WiFi的時(shí)候來(lái)處理。 關(guān)于JobScheduler的更多知識(shí)可以參考http://hukai.me/android-training-course-in-chinese/background-jobs/scheduling/index.html

14)Understanding Battery Drain on Android

電量消耗的計(jì)算與統(tǒng)計(jì)是一件麻煩而且矛盾的事情,記錄電量消耗本身也是一個(gè)費(fèi)電量的事情。唯一可行的方案是使用第三方監(jiān)測(cè)電量的設(shè)備,這樣才能夠獲取到真實(shí)的電量消耗。

當(dāng)設(shè)備處于待機(jī)狀態(tài)時(shí)消耗的電量是極少的,以N5為例,打開(kāi)飛行模式,可以待機(jī)接近1個(gè)月?墒屈c(diǎn)亮屏幕,硬件各個(gè)模塊就需要開(kāi)始工作,這會(huì)需要消耗很多電量。

使用WakeLock或者JobScheduler喚醒設(shè)備處理定時(shí)的任務(wù)之后,一定要及時(shí)讓設(shè)備回到初始狀態(tài)。每次喚醒無(wú)線信號(hào)進(jìn)行數(shù)據(jù)傳遞,都會(huì)消耗很多電量,它比WiFi等操作更加的耗電,詳情請(qǐng)關(guān)注http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html

\

修復(fù)電量的消耗是另外一個(gè)很大的課題,這里就不展開(kāi)繼續(xù)了。

15)Battery Drain and WakeLocks

高效的保留更多的電量與不斷促使用戶使用你的App來(lái)消耗電量,這是矛盾的選擇題。不過(guò)我們可以使用一些更好的辦法來(lái)平衡兩者。

假設(shè)你的手機(jī)里面裝了大量的社交類(lèi)應(yīng)用,即使手機(jī)處于待機(jī)狀態(tài),也會(huì)經(jīng)常被這些應(yīng)用喚醒用來(lái)檢查同步新的數(shù)據(jù)信息。Android會(huì)不斷關(guān)閉各種硬件來(lái)延長(zhǎng)手機(jī)的待機(jī)時(shí)間,首先屏幕會(huì)逐漸變暗直至關(guān)閉,然后CPU進(jìn)入睡眠,這一切操作都是為了節(jié)約寶貴的電量資源。但是即使在這種睡眠狀態(tài)下,大多數(shù)應(yīng)用還是會(huì)嘗試進(jìn)行工作,他們將不斷的喚醒手機(jī)。一個(gè)最簡(jiǎn)單的喚醒手機(jī)的方法是使用PowerManager.WakeLock的API來(lái)保持CPU工作并防止屏幕變暗關(guān)閉。這使得手機(jī)可以被喚醒,執(zhí)行工作,然后回到睡眠狀態(tài)。知道如何獲取WakeLock是簡(jiǎn)單的,可是及時(shí)釋放WakeLock也是非常重要的,不恰當(dāng)?shù)氖褂肳akeLock會(huì)導(dǎo)致嚴(yán)重錯(cuò)誤。例如網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)返回時(shí)間不確定,導(dǎo)致本來(lái)只需要10s的事情一直等待了1個(gè)小時(shí),這樣會(huì)使得電量白白浪費(fèi)了。這也是為何使用帶超時(shí)參數(shù)的wakelock.acquice()方法是很關(guān)鍵的。但是僅僅設(shè)置超時(shí)并不足夠解決問(wèn)題,例如設(shè)置多長(zhǎng)的超時(shí)比較合適?什么時(shí)候進(jìn)行重試等等?

解決上面的問(wèn)題,正確的方式可能是使用非精準(zhǔn)定時(shí)器。通常情況下,我們會(huì)設(shè)定一個(gè)時(shí)間進(jìn)行某個(gè)操作,但是動(dòng)態(tài)修改這個(gè)時(shí)間也許會(huì)更好。例如,如果有另外一個(gè)程序需要比你設(shè)定的時(shí)間晚5分鐘喚醒,最好能夠等到那個(gè)時(shí)候,兩個(gè)任務(wù)捆綁一起同時(shí)進(jìn)行,這就是非精確定時(shí)器的核心工作原理。我們可以定制計(jì)劃的任務(wù),可是系統(tǒng)如果檢測(cè)到一個(gè)更好的時(shí)間,它可以推遲你的任務(wù),以節(jié)省電量消耗。

\

這正是JobScheduler API所做的事情。它會(huì)根據(jù)當(dāng)前的情況與任務(wù),組合出理想的喚醒時(shí)間,例如等到正在充電或者連接到WiFi的時(shí)候,或者集中任務(wù)一起執(zhí)行。我們可以通過(guò)這個(gè)API實(shí)現(xiàn)很多免費(fèi)的調(diào)度算法。

從Android 5.0開(kāi)始發(fā)布了Battery History Tool,它可以查看程序被喚醒的頻率,又誰(shuí)喚醒的,持續(xù)了多長(zhǎng)的時(shí)間,這些信息都可以獲取到。

請(qǐng)關(guān)注程序的電量消耗,用戶可以通過(guò)手機(jī)的設(shè)置選項(xiàng)觀察到那些耗電量大戶,并可能決定卸載他們。所以盡量減少程序的電量消耗是非常有必要的。

關(guān)鍵詞:Android性能優(yōu)化