周星馳的電影《功夫》里面借火云邪神之口說出了一句至理名言:“天下武功,唯快不破”儡蔓。
在移動(dòng)互聯(lián)網(wǎng)時(shí)代郭蕉,同樣如此,如何把握住這個(gè)時(shí)機(jī)喂江,迅速開發(fā)出產(chǎn)品,成為至關(guān)重要的一環(huán)旁振。但是快速開發(fā)出來的產(chǎn)品代碼運(yùn)行的效率怎么樣呢获询?我們的App 給用戶的體驗(yàn)如何呢?
我們的App在低端機(jī)上經(jīng)常ANR拐袜、閃退吉嚣、卡頓等
我們的App在其他分辨率上顯示慘不忍睹?
我們的App在不同網(wǎng)絡(luò)的情況下如何處理的
…
我們的App體驗(yàn)如此之差蹬铺,導(dǎo)致大量的用戶流失尝哆。這些迫使我們認(rèn)識(shí)到性能優(yōu)化是非常重要,某種程度上甚至超過了新功能的開發(fā)甜攀。
也驗(yàn)證了一句話:“別人有的我們也有秋泄,而且比他們的要好要快琐馆。
UI性能問題分析優(yōu)化
UI可謂是一個(gè)應(yīng)用的臉,所以每一款應(yīng)用在開發(fā)階段我們的交互恒序、視覺都拼命的想讓它變得自然大方美麗瘦麸,可是現(xiàn)實(shí)總是不盡人意,視覺和交互總會(huì)覺得開發(fā)做出來的應(yīng)用用上去感覺不自然歧胁,沒有達(dá)到他們心目中的自然流暢細(xì)節(jié)滋饲; 用戶要是能夠感覺出來,少則影響心情喊巍,多則卸載應(yīng)用屠缭;所以一 個(gè)應(yīng)用的UI顯示性能問題就不得不被開發(fā)人員重視
Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào),觸發(fā)對(duì)UI進(jìn)行渲染崭参,那么整個(gè)過程如果保證在16ms以內(nèi)就能達(dá)到一個(gè)流暢的畫面呵曹。如果系統(tǒng)發(fā)出了VSYNC信號(hào),而此時(shí)無法進(jìn)行渲染阵翎,還在做別的操作逢并,那么就會(huì)導(dǎo)致丟幀的現(xiàn)象。這樣的話郭卫,繪制就會(huì)在下一個(gè)16ms的時(shí)候才進(jìn)行繪制砍聊,即使只丟一幀,用戶也會(huì)發(fā)現(xiàn)卡頓的
所謂的卡頓其實(shí)是可以量化的贰军,每次是否能夠成功渲染是非常重要的問題玻蝌,16ms能否完整的做完一次操作直接決定了UI卡頓與否。
這是因?yàn)槿搜叟c大腦之間的協(xié)作無法感知超過60fps的畫面更新词疼。12fps大概類似手動(dòng)快速翻動(dòng)書籍的幀率俯树,這明顯是可以感知到不夠順滑的。24fps使得人眼感知的是連續(xù)線性的運(yùn)動(dòng)贰盗,這其實(shí)是歸功于運(yùn)動(dòng)模糊的 效果许饿。24fps是電影膠圈通常使用的幀率,因?yàn)檫@個(gè)幀率已經(jīng)足夠支撐大部分電影畫面需要表達(dá)的內(nèi)容舵盈,同時(shí)能夠最大的減少費(fèi)用支出陋率。但是要順暢表現(xiàn)絢麗的畫面內(nèi)容的,此時(shí)就需要用到60fps來達(dá)到想要的效果秽晚,當(dāng)然超過60fps是沒有必要的
VSync機(jī)制就像是一臺(tái)轉(zhuǎn)速固定的發(fā)動(dòng)機(jī)(60轉(zhuǎn)/s)瓦糟。每一轉(zhuǎn)會(huì)帶動(dòng)著去做一些UI相關(guān)的事情,但不是每一轉(zhuǎn)都會(huì)有工作去做(就像有時(shí)在空擋赴蝇,有時(shí)在D檔)菩浙。有時(shí)候因?yàn)楦鞣N阻力某一圈工作量比較重超過了16.6ms,那么這臺(tái)發(fā)動(dòng)機(jī)這秒內(nèi)就不是60轉(zhuǎn)了,當(dāng)然也有可能被其他因素影響劲蜻,比如給油不足(主線程里干的活太多)等等陆淀,就會(huì)出現(xiàn)轉(zhuǎn)速降低的狀況。我們把這個(gè)轉(zhuǎn)速叫做流暢度斋竞。
應(yīng)用UI卡頓常見原因
我們?cè)谑褂肁pp時(shí)會(huì)發(fā)現(xiàn)有些界面啟動(dòng)卡頓倔约、動(dòng)畫不流暢廓脆、列表等滑動(dòng)時(shí)也會(huì)卡頓共郭,究其原因,很多都是丟幀導(dǎo)致的娄涩;通過上面卡頓原理的簡(jiǎn)單說明鳄袍,我們從應(yīng)用開發(fā)的角度往回推理可以得出常見卡頓原因绢要,如下:
人為在UI線程中做輕微耗時(shí)操作,導(dǎo)致UI線程卡頓拗小;
布局Layout過于復(fù)雜重罪,無法在16ms內(nèi)完成渲染;
View過度繪制哀九,導(dǎo)致某些像素在同一幀時(shí)間內(nèi)被繪制多次剿配,從而使CPU或GPU負(fù)載過重;
View頻繁的觸發(fā)measure阅束、layout呼胚,導(dǎo)致measure、layout累計(jì)耗時(shí)過多及整個(gè)View頻繁的重新渲染息裸;
內(nèi)存頻繁觸發(fā)GC過多(同一幀中頻繁創(chuàng)建內(nèi)存)蝇更,導(dǎo)致暫時(shí)阻塞渲染操作;虛擬機(jī)在執(zhí)行GC垃圾回收操作時(shí)所有線程(包括UI線程)都需要暫停呼盆,當(dāng)GC垃圾回收完成之后所有線程才能夠繼續(xù)執(zhí)行 也就是說當(dāng)在16ms內(nèi)進(jìn)行渲染等操作時(shí)如果剛好遇上大量GC操作則會(huì)導(dǎo)致渲染時(shí)間明顯不足年扩,也就從而導(dǎo)致了丟幀卡頓問題。
冗余資源及邏輯等導(dǎo)致加載和執(zhí)行緩慢访圃;臭名昭著的ANR厨幻;
可以看見,上面這些導(dǎo)致卡頓的原因都是我們平時(shí)開發(fā)中非常常見的腿时。有些人可能會(huì)覺得自己的應(yīng)用用著還蠻OK的克胳,其實(shí)那是因?yàn)槟銢]進(jìn)行一些瞬時(shí)測(cè)試和壓力測(cè)試,一旦在這種環(huán)境下運(yùn)行你的App你就會(huì)發(fā)現(xiàn)很多性能問題圈匆。
應(yīng)用的主UI線程的概念及其重要性是每個(gè)Android開發(fā)者都應(yīng)理解。當(dāng)一個(gè)應(yīng)用啟動(dòng)捏雌,系統(tǒng)會(huì)為應(yīng)用創(chuàng)建一個(gè)名為“main”的主線程跃赚。這個(gè)主線程(也就是UI主線程)主要負(fù)責(zé)把事件分發(fā)給合適的view或者widget。例如,如果你點(diǎn)擊了屏幕上的一個(gè)按鈕纬傲,UI線程會(huì)把點(diǎn)擊時(shí)間交給view處理满败,view接到事件后會(huì)設(shè)置它的pressed狀態(tài),然后向事件隊(duì)列中發(fā)送一個(gè)invalidate請(qǐng)求叹括。 UI線程會(huì)依次讀取隊(duì)列并且告訴view去重繪自己算墨。
解決方法
分析UI卡頓我一般都借助工具,通過工具一般都可以直觀的分析出問題原因汁雷,從而反推尋求優(yōu)化方案净嘀,具體如下細(xì)說各種強(qiáng)大的工具。
- 使用GPU過繪分析UI過度繪制問題
如果我們粉刷過一個(gè)房間或一所房子侠讯,就會(huì)知道給墻壁涂上顏色需要做大量的工作挖藏。假如你還要重新粉刷一次的話,第二次粉刷的顏色會(huì)覆蓋住第一次的顏色厢漩,第一次的顏色就永遠(yuǎn)不可見了膜眠,等于你第一次粉刷做的大量工作就完全被浪費(fèi)掉。
同樣的道理溜嗜,如果在我們的應(yīng)用程序中浪費(fèi)精力去繪制一些東西同樣會(huì)產(chǎn)生性能問題宵膨。過度繪制這個(gè)名詞就是用來描述屏幕上一個(gè)像素在1幀中被重繪了多少次
過度繪制其實(shí)是一個(gè)性能和設(shè)計(jì)的交叉點(diǎn)。我們?cè)谠O(shè)計(jì)上追求很華麗的視覺效果炸宵,但一般來說這種視覺效果會(huì)采用非常多的層疊組件來實(shí)現(xiàn)辟躏,這時(shí)候就會(huì)帶來過度繪制的問題。
過度繪制也許是因?yàn)槟愕腢I布局中存在大量重疊的view焙压,但一個(gè)更為普遍的情況是因?yàn)槟切┎槐匾闹丿B著的背景鸿脓。例如某個(gè)Activity有一個(gè)背景,Layout也有自己的背景涯曲,同時(shí)它的子View又分別有自己的背景野哭。
設(shè)置-開發(fā)者選項(xiàng)-調(diào)試GPU過度繪制(過度渲染等,不同機(jī)器可能不同)


開啟后幻件,啟動(dòng)我們的應(yīng)用拨黔,可以看到各種顏色的區(qū)域,其中:

藍(lán)色 1x過度繪制
綠色 2x過度繪制
淡紅色 3x過度繪制
紅色 超過4x過度繪制
最理想的是藍(lán)色,一個(gè)像素只繪制一次。合格的頁面繪制是白色绰沥、藍(lán)色為主篱蝇,顏色越淺越好。
過度繪制產(chǎn)生的原因:
太多重疊的背景
太多疊加的View,本來這個(gè)UI布局就很復(fù)雜或者你是為了追求一個(gè)炫麗的視覺效果徽曲,這都有可能使得很多view疊加在一起零截。
復(fù)雜的layout層級(jí)
我們可以根據(jù)這些原因來查找代碼中存在的問題,并做出適當(dāng)?shù)男薷?/p>
- Hierarchy Viewer
Hierarchy Viewer是隨AndroidSDK發(fā)布的工具,位置在tools文件夾下秃臣,名為hierarchyviewer.bat涧衙。它是Android自帶的非常有用而且使用簡(jiǎn)單的工具哪工,可以幫助我們更好地檢視和設(shè)計(jì)用戶界面(UI),從可視化的角度直觀地獲得UI布局設(shè)計(jì)結(jié)構(gòu)和各種屬性的信息弧哎,幫助我們優(yōu)化布局設(shè)計(jì)雁比;
一個(gè)Activity的View樹,通過這個(gè)樹可以分析出View嵌套的冗余層級(jí)
Hierarchy Viewer是隨AndroidSDK發(fā)布的工具撤嫩,位置在tools文件夾下偎捎,名為hierarchyviewer.bat。它是Android自帶的非常有用而且使用簡(jiǎn)單的工具序攘,可以幫助我們更好地檢視和設(shè)計(jì)用戶界面(UI)茴她,從可視化的角度直觀地獲得UI布局設(shè)計(jì)結(jié)構(gòu)和各種屬性的信息,幫助我們優(yōu)化布局設(shè)計(jì)两踏;
類似上圖可以很方便的查看到當(dāng)前View的許多信息败京;上圖最底那三個(gè)彩色原點(diǎn)代表了當(dāng)前View的性能指標(biāo),從左到右依次代表測(cè)量梦染、布局赡麦、繪制的渲染時(shí)間,紅色和黃色的點(diǎn)代表速度渲染較慢的View(當(dāng)然了帕识,有些時(shí)候較慢不代表有問題泛粹,譬如ViewGroup子節(jié)點(diǎn)越多、結(jié)構(gòu)越復(fù)雜肮疗,性能就越差)
- lint
作為移動(dòng)應(yīng)用開發(fā)者晶姊,我們總希望發(fā)布的apk文件越小越好,不希望資源文件沒有用到的圖片資源也被打包進(jìn)apk伪货,不希望應(yīng)用中使用了高于minSdk的api们衙,也不希望AndroidManifest文件存在異常,lint就能解決我們的這些問題碱呼。靜態(tài)代碼分析工具蒙挑,無需運(yùn)行,無需測(cè)試用例 掃描整個(gè)項(xiàng)目愚臀,分析以下潛在的問題忆蚀,分類指出問題描述、問題位置姑裂,并提供合理的修改建議
Android lint是在ADT 16提供的新工具馋袜,它是一個(gè)代碼掃描工具,能夠幫助我們識(shí)別代碼結(jié)構(gòu)存在的問題舶斧,主要包括:
1)性能 布局性能(以前是 layoutopt工具欣鳖,可以解決無用布局、嵌套太多茴厉、布局太多观堂、overdraw) 其他性能(如:draw/layout 時(shí)進(jìn)行對(duì)象的聲明等)
2)未使用到資源让网、資源缺少(不同資源的適配)
3)有更高性能的資源替換 ---- eg:SparseBooleanArray SparseIntArray
4)國(guó)際化問題(硬編碼)
5)圖標(biāo)的問題(重復(fù)的圖標(biāo),錯(cuò)誤的大惺邸)
6)可用性問題(如不指定的文本字段的輸入型)
7)manifest文件的錯(cuò)誤 -- 未注冊(cè)activity service等等
8)內(nèi)存泄露 --- 如:handle的不當(dāng)使用 。
9)占內(nèi)存的資源及時(shí)回收 --- 如:TypedArray未回收資源等
如何使用lint檢索我們代碼中存在的問題呢而账?
在編輯窗口郵件調(diào)出菜單選項(xiàng)

然后彈出選擇檢測(cè)目標(biāo)project/module/files等

通過lint我們可以直觀的看到我們代碼中存在的不易被發(fā)現(xiàn)的問題,
下方的視圖是檢索出我的代碼中存在問題的列表胰坟,點(diǎn)擊后可以在右邊視圖展示問題詳細(xì)信息,以及給出我們一些建議



- 布局優(yōu)化建議
布局優(yōu)化的一些建議
首先刪除布局中無用的控件和層級(jí),其次有選擇的使用性能較低的ViewGroup,比如RelativeLayout.如果布局中既可以使用LinearLayout也可以使用RelativeLayout,那么就采用LinearLayout,這是因?yàn)镽elativeLayout的功能比較復(fù)雜,它的布局過程需要花費(fèi)更多的CPU時(shí)間.
布局優(yōu)化的另外一種手段是抽象布局標(biāo)簽
采用<include>標(biāo)簽,<merge>標(biāo)簽和泞辐,<include>標(biāo)簽主要用于布局的重用, ,<merge>標(biāo)簽一般和<include>配合使用,它可以降低減少布局的層級(jí), 某布局作為子布局被其他布局include時(shí)笔横,使用merge當(dāng)作該布局的頂節(jié)點(diǎn),這樣在被引入時(shí)頂結(jié)點(diǎn)會(huì)自動(dòng)被忽略咐吼,而將其子節(jié)點(diǎn)全部合并到主布局中吹缔。去除不必要的嵌套和View節(jié)點(diǎn)
首次不需要使用的節(jié)點(diǎn)設(shè)置為GONE或使用viewstub
viewstub標(biāo)簽同include標(biāo)簽一樣可以用來引入一個(gè)外部布局,不同的是锯茄,viewstub引入的布局默認(rèn)不會(huì)擴(kuò)張厢塘,即既不會(huì)占用顯示也不會(huì)占用位置,按需加載,當(dāng)需需要時(shí)才會(huì)將ViewStup中的布局加載到內(nèi)存,這提高了程序的初始功率.
從而在解析layout時(shí)節(jié)省cpu和內(nèi)存肌幽。viewstub常用來引入那些默認(rèn)不會(huì)顯示晚碾,只在特殊情況下顯示的布局,如進(jìn)度布局喂急、網(wǎng)絡(luò)失敗顯示的刷新布局格嘁、信息出錯(cuò)出現(xiàn)的提示布局等。減少不必要的infalte
對(duì)于inflate的布局可以直接緩存廊移,用全局變量代替局部變量糕簿,避免下次需再次inflate用SurfaceView或TextureView代替普通View
SurfaceView或TextureView可以通過將繪圖操作移動(dòng)到另一個(gè)單獨(dú)線程上提高性能。
普通View的繪制過程都是在主線程(UI線程)中完成狡孔,如果某些繪圖操作影響性能就不好優(yōu)化了懂诗,這時(shí)我們可以考慮使用SurfaceView和TextureView,他們的繪圖操作發(fā)生在UI線程之外的另一個(gè)線程上步氏。
因?yàn)镾urfaceView在常規(guī)視圖系統(tǒng)之外响禽,所以無法像常規(guī)試圖一樣移動(dòng)、縮放或旋轉(zhuǎn)一個(gè)SurfaceView荚醒。TextureView是Android4.0引入的芋类,除了與SurfaceView一樣在單獨(dú)線程繪制外,還可以像常規(guī)視圖一樣被改變界阁。
通過上面UI性能的原理侯繁、原因、工具分析總結(jié)可以發(fā)現(xiàn)泡躯,我們?cè)陂_發(fā)應(yīng)用時(shí)一定要時(shí)刻重視性能問題贮竟,如若真的沒留意出現(xiàn)了性能問題丽焊,不妨使用上面的一些案例方式進(jìn)行分析。但是那終歸是補(bǔ)救措施咕别,在我們知道上面UI卡頓原理之后我們應(yīng)該盡量從項(xiàng)目代碼架編寫時(shí)就避免一些UI性能問題技健,
當(dāng)然了,上面只是列出了我們項(xiàng)目中常見的一些UI性能注意事項(xiàng)而已惰拱,相信還有很多其他的情況這里沒有說到雌贱,歡迎補(bǔ)充。還有一點(diǎn)就是我們上面所謂的UI性能優(yōu)化分析總結(jié)等都是建議性的偿短,因?yàn)樾阅苓@個(gè)問題是一個(gè)涉及面很廣很泛的問題欣孤,有些優(yōu)化不是必需的,有些優(yōu)化是必需的昔逗,有些優(yōu)化掉以后又是得不償失的降传,所以我們一般著手解決那些必須的就可以了。
Memory內(nèi)存性能問題
說完了應(yīng)用開發(fā)中的UI性能問題后我們就該來關(guān)注應(yīng)用開發(fā)中的另一個(gè)重要的性能問題了勾怒,那就是內(nèi)存性能優(yōu)化分析婆排。Android其實(shí)就是嵌入式設(shè)備,嵌入式設(shè)備核心關(guān)注點(diǎn)之一就是內(nèi)存資源控硼;有人說現(xiàn)在的設(shè)備都在硬件配置已經(jīng)很厲害了泽论,所以內(nèi)存不會(huì)再像以前那么緊張了,其實(shí)這句話聽著沒錯(cuò)卡乾,但為啥再牛逼配置的Android設(shè)備還是越用系統(tǒng)越卡呢翼悴?
大家先想一個(gè)問題,假設(shè)有一個(gè)內(nèi)存為1G的Android設(shè)備幔妨,上面運(yùn)行了一個(gè)非常非常吃內(nèi)存的應(yīng)用鹦赎,如果沒有任何機(jī)制的情況下是不是用著用著整個(gè)設(shè)備會(huì)因?yàn)槲覀冞@個(gè)應(yīng)用把1G內(nèi)存吃光然后整個(gè)系統(tǒng)運(yùn)行癱瘓呢
為了能夠使得Android應(yīng)用程序安全且快速的運(yùn)行,Android 的每個(gè)應(yīng)用程序都會(huì)使用一個(gè)專有的Dalvik虛擬機(jī)實(shí)例來運(yùn)行误堡,也就是說每個(gè)應(yīng)用程序都是在屬于自己的進(jìn)程中運(yùn)行的古话。一方面,如果程序在運(yùn)行過程中出現(xiàn)了內(nèi)存溢出的問題锁施,僅僅會(huì)使得自己的進(jìn)程被殺掉陪踩,而不會(huì)影響其他進(jìn)程(如果是system_process 等系統(tǒng)進(jìn)程出問題的話,則會(huì)引起系統(tǒng)重啟)悉抵。另一方面Android為這些進(jìn)程分配了內(nèi)存使用上限肩狂,如果應(yīng)用進(jìn)程使用的內(nèi)存超過了這個(gè)上限, 就會(huì)被殺掉姥饰。
Android把這些進(jìn)程都保留在內(nèi)存中傻谁,直到系統(tǒng)需要更多內(nèi)存時(shí)才選擇性的釋放一些,保留在內(nèi)存中是當(dāng)再次啟動(dòng)這些保留在內(nèi)存的進(jìn)程時(shí)可以明顯提高啟動(dòng)速度列粪,不需要再去加載审磁。在Android系統(tǒng)中框架會(huì)定義如下幾類進(jìn)程谈飒、在系統(tǒng)內(nèi)存達(dá)到規(guī)定的不同level閾值時(shí)觸發(fā)清空不同level的進(jìn)程類型。
Android內(nèi)存泄露性能分析
千里之堤态蒂, 毀于蟻穴
在Android開發(fā)過程中杭措,最為讓我們頭疼的就是內(nèi)存的泄露問題了,很可能你很小的一個(gè)錯(cuò)誤都會(huì)引起內(nèi)存的泄露吃媒,
一些對(duì)象有著有限的生命周期赘那。當(dāng)這些對(duì)象所要做的事情完成了,我們希望他們會(huì)被回收掉拱礁。但是如果這個(gè)對(duì)象被超過自己生命周期以外的對(duì)象強(qiáng)引用呢灶,那么在我們期待這個(gè)對(duì)象生命周期結(jié)束的時(shí)候被收回的時(shí)候,它是不會(huì)被回收的缨睡。它還會(huì)占用內(nèi)存奖年,這就造成了內(nèi)存泄露陋守。持續(xù)累加水评,內(nèi)存很快被耗盡之碗。
在Java中有些對(duì)象的生命周期是有限的季希,當(dāng)它們完成了特定的邏輯后將會(huì)被垃圾回收;
但是友浸,如果在對(duì)象的生命周期本來該被垃圾回收時(shí)這個(gè)對(duì)象還被別的對(duì)象所持有引用,那就會(huì)導(dǎo)致內(nèi)存泄漏伦意;這樣的后果就是隨著我們的應(yīng)用被長(zhǎng)時(shí)間使用,他所占用的內(nèi)存越來越大离钝。
造成內(nèi)存泄露泄露的最核心原理就是一個(gè)對(duì)象持有了超過自己生命周期以外的對(duì)象強(qiáng)引用導(dǎo)致該對(duì)象無法被正常垃圾回收;可以發(fā)現(xiàn)浪读,應(yīng)用內(nèi)存泄露是個(gè)相當(dāng)棘手重要的問題瑟啃,我們必須重視。
- Android應(yīng)用開發(fā)規(guī)避內(nèi)存泄露建議
關(guān)于規(guī)避內(nèi)存泄露我在下面列出了我在項(xiàng)目中經(jīng)常遇見的一些情況揩尸,肯定不全面,歡迎補(bǔ)充错负!
Activity中生成的對(duì)象原則上是應(yīng)該在Activity生命周期結(jié)束之后就釋放的。Activity對(duì)象本身也是犹撒,所以應(yīng)該盡量避免有appliction進(jìn)程級(jí)別的對(duì)象來引用Activity級(jí)別的對(duì)象,如果有的話也應(yīng)該在Activity結(jié)束的時(shí)候解引用粒褒。如不應(yīng)用applicationContext在Activity中獲取資源识颊。
線程未終止造成的內(nèi)存泄露;譬如在Activity中關(guān)聯(lián)了一個(gè)生命周期超過Activity的Thread,在退出Activity時(shí)切記結(jié)束線程抠艾。一個(gè)典型的例子就是HandlerThread的run方法是一個(gè)死循環(huán),它不會(huì)自己結(jié)束蛙酪,線程的生命周期超過了Activity生命周期脸狸,我們必須手動(dòng)在Activity的銷毀方法中中調(diào)運(yùn)thread.getLooper().quit();才不會(huì)泄露。
對(duì)象的注冊(cè)與反注冊(cè)沒有成對(duì)出現(xiàn)造成的內(nèi)存泄露藐俺;BroadCastReceiver、Service 解綁泥彤。
創(chuàng)建與關(guān)閉沒有成對(duì)出現(xiàn)造成的泄露欲芹;譬如Cursor資源必須手動(dòng)關(guān)閉,WebView必須手動(dòng)銷毀吟吝,I/O等對(duì)象必須手動(dòng)關(guān)閉等菱父。
不要在執(zhí)行頻率很高的方法或者循環(huán)中創(chuàng)建對(duì)象,如onDraw中創(chuàng)建新的局部對(duì)象剑逃。
避免代碼設(shè)計(jì)模式的錯(cuò)誤造成內(nèi)存泄露浙宜;譬如循環(huán)引用,A持有B蛹磺,B持有C粟瞬,C持有A,這樣的設(shè)計(jì)誰都得不到釋放萤捆。
屬性動(dòng)畫導(dǎo)致的內(nèi)存泄露從Android 3.0開始,Google提供了屬性動(dòng)畫,屬性動(dòng)畫中有一類無限循環(huán)的動(dòng)畫,如果在Activity中播放此類動(dòng)畫且沒有在onDestroy中去停止動(dòng)畫,那么動(dòng)畫會(huì)一直播放下去,盡管已經(jīng)無法在界面上看到動(dòng)畫效果了,并且這個(gè)時(shí)候Activity的View會(huì)被動(dòng)畫持有,而View又持有了Activity,最終Activity無法被釋放.
內(nèi)存泄露察覺工具
知道了內(nèi)存泄露的概念之后肯定就是想辦法來確認(rèn)自己的項(xiàng)目是否存在內(nèi)存泄露了裙品,那該如何察覺自己項(xiàng)目是否存在內(nèi)存泄露呢?
排查內(nèi)存泄露是一個(gè)全手工的過程
以下幾個(gè)關(guān)鍵步驟:
重現(xiàn)問題俗或。為了重現(xiàn)問題市怎,機(jī)型非常重要,因?yàn)橐恍﹩栴}只在特定的設(shè)備上會(huì)出現(xiàn)辛慰。為了找到特定的機(jī)型区匠,你需要想盡一切辦法,你可能需要去買帅腌,去借驰弄,麻汰。 當(dāng)然,為了確定復(fù)現(xiàn)步驟揩懒,你需要一遍一遍地去嘗試什乙。一切都是非常原始和粗暴的。
在發(fā)生內(nèi)存泄露的時(shí)候已球,把內(nèi)存 Dump 出來臣镣。具體看這里。然后智亮,你需要在 MAT 的內(nèi)存分析工具中反復(fù)查看忆某,找到那些原本該被回收掉的對(duì)象。
計(jì)算這個(gè)對(duì)象到 GC roots 的最短強(qiáng)引用路徑阔蛉。
確定引用路徑中的哪個(gè)引用是不該有的弃舒,然后修復(fù)問題。
很復(fù)雜對(duì)吧状原?
如果有一個(gè)類庫能在發(fā)生 OOM 之前把這些事情全部都搞定聋呢,然后你只要修復(fù)這些問題就好了,豈不妙哉颠区!
直白的展現(xiàn)Android中的內(nèi)存泄露
LeakCanary 是一個(gè)檢測(cè)內(nèi)存泄露的開源類庫削锰。我們可以在debug 包種輕松檢測(cè)內(nèi)存泄露。
只需幾行代碼毕莱,LeakCanary就能自動(dòng)檢測(cè)Activity的泄漏:比如我們?cè)赼ndroid studio中可以直接引入這個(gè)類庫
當(dāng)存在內(nèi)存泄漏時(shí)器贩,會(huì)有一個(gè)通知和良好的展示界面:



如何使用:
在 build.gradle 中加入引用,不同的編譯使用不同的引用:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
}
在 Application 中:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
這樣朋截,就萬事俱備了蛹稍! 在 debug build 中,如果檢測(cè)到某個(gè) activity 有內(nèi)存泄露部服,LeakCanary 就是自動(dòng)地顯示一個(gè)通知唆姐。
Android內(nèi)存溢出OOM
上面我們探討了Android內(nèi)存管理和應(yīng)用開發(fā)中的內(nèi)存泄露問題,可以知道內(nèi)存泄露一般影響就是導(dǎo)致應(yīng)用卡頓饲宿,但是極端的影響是使應(yīng)用掛掉厦酬。前面也提到過應(yīng)用的內(nèi)存分配是有一個(gè)閾值的,超過閾值就會(huì)出問題瘫想,這里我們就來看看這個(gè)問題—–內(nèi)存溢出(OOM–OutOfMemoryError)仗阅。

內(nèi)存溢出的主要導(dǎo)致原因有兩類:
應(yīng)用代碼存在內(nèi)存泄露,長(zhǎng)時(shí)間積累無法釋放導(dǎo)致OOM国夜;
應(yīng)用的某些邏輯操作瘋狂的消耗掉大量?jī)?nèi)存(譬如加載一張不經(jīng)過處理的超大超高清圖片等)導(dǎo)致超過閾值OOM减噪;
可以發(fā)現(xiàn),無論哪種類型,導(dǎo)致內(nèi)存溢出(OutOfMemoryError)的核心原因就是應(yīng)用的內(nèi)存超過閾值了筹裕。

- Android應(yīng)用規(guī)避內(nèi)存溢出OOM建議
還是那句話醋闭,等待OOM發(fā)生是為時(shí)已晚的事,我們應(yīng)該將其扼殺于萌芽之中朝卒,至于如何在開發(fā)中規(guī)避OOM证逻,如下給出一些我們應(yīng)用開發(fā)中的常用的策略建議:
時(shí)刻記得不要加載過大的Bitmap對(duì)象;譬如對(duì)于類似圖片加載我們要通過BitmapFactory.Options,對(duì)相關(guān)參數(shù)進(jìn)行配置來減少加載的像素,設(shè)置圖片的一些采樣比率和復(fù)用等,在BitmapFactory.Options中指定inSampleSize參數(shù)抗斤,這將表明一旦加載時(shí)結(jié)果Bitmap圖像所占的比例囚企。例如,在這里將inSampleSize設(shè)置為4瑞眼,這會(huì)產(chǎn)生一幅大小是原始圖像大小1/4的圖像
優(yōu)化界面交互過程中頻繁的內(nèi)存使用龙宏;譬如在列表等操作中只加載可見區(qū)域的Bitmap、滑動(dòng)時(shí)不加載伤疙、停止滑動(dòng)后再開始加載银酗。
避免各種內(nèi)存泄露的存在導(dǎo)致OOM。
對(duì)批量加載等操作進(jìn)行緩存設(shè)計(jì)徒像,譬如列表圖片顯示黍特,Adapter的convertView緩存等。
盡可能的復(fù)用資源锯蛀;譬如系統(tǒng)本身有很多字符串衅澈、顏色、圖片谬墙、動(dòng)畫、樣式以及簡(jiǎn)單布局等資源可供我們直接使用经备,我們自己也要盡量復(fù)用style等資源達(dá)到節(jié)約內(nèi)存拭抬。
對(duì)于有緩存等存在的應(yīng)用盡量實(shí)現(xiàn)onLowMemory()和onTrimMemory()方法來需要的時(shí)候釋放緩存。
盡量使用線程池替代多線程操作侵蒙,這樣可以節(jié)約內(nèi)存及CPU占用率造虎。
盡量管理好自己的Service、Thread等后臺(tái)的生命周期纷闺,不要浪費(fèi)內(nèi)存占用算凿。
盡量在做一些大內(nèi)存分配等可疑內(nèi)存操作時(shí)進(jìn)行try catch操作,避免不必要的應(yīng)用閃退犁功。
盡量的優(yōu)化自己的代碼氓轰,減少冗余,避免類加載時(shí)浪費(fèi)內(nèi)存浸卦。
可以發(fā)現(xiàn)署鸡,上面只是列出了我們開發(fā)中常見的導(dǎo)致OOM異常的一些規(guī)避原則,還有很多相信還沒有列出來,大家可以自行追加參考即可靴庆。
- Android應(yīng)用OnTrimMemory()實(shí)現(xiàn)性能建議
OnTrimMemory是Android 4.0之后加入的一個(gè)回調(diào)方法时捌,作用是通知應(yīng)用在不同的情況下進(jìn)行自身的內(nèi)存釋放,以避免被系統(tǒng)直接殺掉炉抒,提高應(yīng)用程序的用戶體驗(yàn)奢讨。系統(tǒng)會(huì)根據(jù)當(dāng)前不同等級(jí)的內(nèi)存使用情況調(diào)用這個(gè)方法,并且傳入當(dāng)前內(nèi)存等級(jí)焰薄,這個(gè)等級(jí)有很多種拿诸,我們可以依據(jù)情況實(shí)現(xiàn)不同的等級(jí).
可以實(shí)現(xiàn)OnTrimMemory方法的系統(tǒng)組件有Application、Activity蛤奥、Fragement佳镜、Service、ContentProvider凡桥;
關(guān)于OnTrimMemory釋放哪些內(nèi)存其實(shí)在架構(gòu)階段就要考慮清楚哪些對(duì)象是要常駐內(nèi)存的蟀伸,哪些是伴隨組件周期存在的,一般需要釋放的都是緩存缅刽。
OnLowMemory是Android提供的API啊掏,在系統(tǒng)內(nèi)存不足,所有后臺(tái)程序(優(yōu)先級(jí)為background的進(jìn)程衰猛,不是指后臺(tái)運(yùn)行的進(jìn)程)都被殺死時(shí)迟蜜,系統(tǒng)會(huì)調(diào)用OnLowMemory。
TRIM_MEMORY_COMPLETE:內(nèi)存不足啡省,并且該進(jìn)程在后臺(tái)進(jìn)程列表最后一個(gè)娜睛,馬上就要被清理
TRIM_MEMORY_MODERATE:內(nèi)存不足,并且該進(jìn)程在后臺(tái)進(jìn)程列表的中部卦睹。
TRIM_MEMORY_BACKGROUND:內(nèi)存不足畦戒,并且該進(jìn)程是后臺(tái)進(jìn)程。
TRIM_MEMORY_UI_HIDDEN:內(nèi)存不足结序,并且該進(jìn)程的UI已經(jīng)不可見了障斋。
以上4個(gè)是4.0增加
TRIM_MEMORY_RUNNING_CRITICAL:內(nèi)存不足(后臺(tái)進(jìn)程不足3個(gè)),并且該進(jìn)程優(yōu)先級(jí)比較高徐鹤,需要清理內(nèi)存
TRIM_MEMORY_RUNNING_LOW:內(nèi)存不足(后臺(tái)進(jìn)程不足5個(gè))垃环,并且該進(jìn)程優(yōu)先級(jí)比較高,需要清理內(nèi)存
TRIM_MEMORY_RUNNING_MODERATE:內(nèi)存不足(后臺(tái)進(jìn)程超過5個(gè))返敬,并且該進(jìn)程優(yōu)先級(jí)比較高遂庄,需要清理內(nèi)存
無論是什么電子設(shè)備的開發(fā),內(nèi)存問題永遠(yuǎn)都是一個(gè)很深?yuàn)W劲赠、無底洞的話題涧团,上面的這些內(nèi)存分析建議也單單只是Android應(yīng)用開發(fā)中一些常見的場(chǎng)景而已只磷,真正的達(dá)到合理的優(yōu)化還是需要很多知識(shí)和功底的。
合理的應(yīng)用架構(gòu)設(shè)計(jì)泌绣、設(shè)計(jì)風(fēng)格選擇钮追、開源庫選擇、代碼邏輯規(guī)范等都會(huì)決定到應(yīng)用的內(nèi)存性能阿迈,我們必須時(shí)刻頭腦清醒的意識(shí)到這些問題潛在的風(fēng)險(xiǎn)與優(yōu)劣元媚,因?yàn)閮?nèi)存優(yōu)化必須要有一個(gè)度,不能一味的優(yōu)化苗沧,亦不能置之不理刊棕。