前面介紹了一些基礎(chǔ)的內(nèi)存管理機(jī)制以及OOM的基礎(chǔ)知識(shí),那么在實(shí)踐操作當(dāng)中荚虚,有哪些指導(dǎo)性的規(guī)則可以參考呢低剔?歸納下來(lái)速梗,可以從四個(gè)方面著手肮塞,首先是減小對(duì)象的內(nèi)存占用,其次是內(nèi)存對(duì)象的重復(fù)利用姻锁,然后是避免對(duì)象的內(nèi)存泄露枕赵,最后是內(nèi)存使用策略優(yōu)化。
1)使用更加輕量的數(shù)據(jù)結(jié)構(gòu)
例如位隶,我們可以考慮使用ArrayMap/SparseArray而不是HashMap等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu)拷窜,下圖演示了HashMap的簡(jiǎn)要工作原理,相比起Android系統(tǒng)專(zhuān)門(mén)為移動(dòng)操作系統(tǒng)編寫(xiě)的ArrayMap容器钓试,在大多數(shù)情況下装黑,都顯示效率低下,更占內(nèi)存弓熏。通常的HashMap的實(shí)現(xiàn)方式更加消耗內(nèi)存恋谭,因?yàn)樗枰粋€(gè)額外的實(shí)例對(duì)象來(lái)記錄Mapping操作。另外挽鞠,SparseArray更加高效在于他們避免了對(duì)key與value的autobox自動(dòng)裝箱疚颊,并且避免了裝箱后的解箱。
2)避免在Android里面使用Enum
3)減小Bitmap對(duì)象的內(nèi)存占用
Bitmap是一個(gè)極容易消耗內(nèi)存的大胖子信认,減小創(chuàng)建出來(lái)的Bitmap的內(nèi)存占用是很重要的材义,通常來(lái)說(shuō)有下面2個(gè)措施:
inSampleSize:縮放比例,在把圖片載入內(nèi)存之前嫁赏,我們需要先計(jì)算出一個(gè)合適的縮放比例其掂,避免不必要的大圖載入。
decode format:解碼格式潦蝇,選擇ARGB_8888/RBG_565/ARGB_4444/ALPHA_8款熬,存在很大差異。
4)使用更小的圖片
在設(shè)計(jì)給到資源圖片的時(shí)候攘乒,我們需要特別留意這張圖片是否存在可以壓縮的空間贤牛,是否可以使用一張更小的圖片。盡量使用更小的圖片不僅僅可以減少內(nèi)存的使用则酝,還可以避免出現(xiàn)大量的InflationException殉簸。假設(shè)有一張很大的圖片被XML文件直接引用,很有可能在初始化視圖的時(shí)候就會(huì)因?yàn)閮?nèi)存不足而發(fā)生InflationException沽讹,這個(gè)問(wèn)題的根本原因其實(shí)是發(fā)生了OOM般卑。
在Android上面最常用的一個(gè)緩存算法是LRU(Least Recently Use)
5)復(fù)用系統(tǒng)自帶的資源
Android系統(tǒng)本身內(nèi)置了很多的資源,例如字符串/顏色/圖片/動(dòng)畫(huà)/樣式以及簡(jiǎn)單布局等等妥泉,這些資源都可以在應(yīng)用程序中直接引用椭微。這樣做不僅僅可以減少應(yīng)用程序的自身負(fù)重,減小APK的大小盲链,另外還可以一定程度上減少內(nèi)存的開(kāi)銷(xiāo)蝇率,復(fù)用性更好迟杂。但是也有必要留意Android系統(tǒng)的版本差異性,對(duì)那些不同系統(tǒng)版本上表現(xiàn)存在很大差異本慕,不符合需求的情況排拷,還是需要應(yīng)用程序自身內(nèi)置進(jìn)去。
6)注意在ListView/GridView等出現(xiàn)大量重復(fù)子組件的視圖里面對(duì)ConvertView的復(fù)用
7)Bitmap對(duì)象的復(fù)用
在ListView與GridView等顯示大量圖片的控件里面需要使用LRU的機(jī)制來(lái)緩存處理好的Bitmap锅尘。
利用inBitmap的高級(jí)特性提高Android系統(tǒng)在Bitmap分配與釋放執(zhí)行效率上的提升(3.0以及4.4以后存在一些使用限制上的差異)监氢。使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經(jīng)存在的內(nèi)存區(qū)域,新解碼的bitmap會(huì)嘗試去使用之前那張bitmap在heap中所占據(jù)的pixel data內(nèi)存區(qū)域藤违,而不是去問(wèn)內(nèi)存重新申請(qǐng)一塊區(qū)域來(lái)存放bitmap浪腐。利用這種特性,即使是上千張的圖片顿乒,也只會(huì)僅僅只需要占用屏幕所能夠顯示的圖片數(shù)量的內(nèi)存大小议街。
8)避免在onDraw方法里面執(zhí)行對(duì)象的創(chuàng)建
類(lèi)似onDraw等頻繁調(diào)用的方法,一定需要注意避免在這里做創(chuàng)建對(duì)象的操作璧榄,因?yàn)樗麜?huì)迅速增加內(nèi)存的使用特漩,而且很容易引起頻繁的gc,甚至是內(nèi)存抖動(dòng)骨杂。
9)StringBuilder
在有些時(shí)候涂身,代碼中會(huì)需要使用到大量的字符串拼接的操作,這種時(shí)候有必要考慮使用StringBuilder來(lái)替代頻繁的“+”搓蚪。
避免對(duì)象的內(nèi)存泄露
內(nèi)存對(duì)象的泄漏蛤售,會(huì)導(dǎo)致一些不再使用的對(duì)象無(wú)法及時(shí)釋放,這樣一方面占用了寶貴的內(nèi)存空間妒潭,很容易導(dǎo)致后續(xù)需要分配內(nèi)存的時(shí)候悍抑,空閑空間不足而出現(xiàn)OOM。顯然杜耙,這還使得每級(jí)Generation的內(nèi)存區(qū)域可用空間變小,gc就會(huì)更容易被觸發(fā)拂盯,容易出現(xiàn)內(nèi)存抖動(dòng)佑女,從而引起性能問(wèn)題。
10)注意Activity的泄漏
通常來(lái)說(shuō)谈竿,Activity的泄漏是內(nèi)存泄漏里面最嚴(yán)重的問(wèn)題团驱,它占用的內(nèi)存多,影響面廣空凸,我們需要特別注意以下兩種情況導(dǎo)致的Activity泄漏:
內(nèi)部類(lèi)引用導(dǎo)致Activity的泄漏
最典型的場(chǎng)景是Handler導(dǎo)致的Activity泄漏嚎花,如果Handler中有延遲的任務(wù)或者是等待執(zhí)行的任務(wù)隊(duì)列過(guò)長(zhǎng),都有可能因?yàn)镠andler繼續(xù)執(zhí)行而導(dǎo)致Activity發(fā)生泄漏呀洲。此時(shí)的引用關(guān)系鏈?zhǔn)荓ooper -> MessageQueue -> Message -> Handler -> Activity紊选。為了解決這個(gè)問(wèn)題啼止,可以在UI退出之前,執(zhí)行remove Handler消息隊(duì)列中的消息與runnable對(duì)象兵罢∠追常或者是使用Static + WeakReference的方式來(lái)達(dá)到斷開(kāi)Handler與Activity之間存在引用關(guān)系的目的。
Activity Context被傳遞到其他實(shí)例中卖词,這可能導(dǎo)致自身被引用而發(fā)生泄漏巩那。
內(nèi)部類(lèi)引起的泄漏不僅僅會(huì)發(fā)生在Activity上,其他任何內(nèi)部類(lèi)出現(xiàn)的地方此蜈,都需要特別留意即横!我們可以考慮盡量使用static類(lèi)型的內(nèi)部類(lèi),同時(shí)使用WeakReference的機(jī)制來(lái)避免因?yàn)榛ハ嘁枚霈F(xiàn)的泄露裆赵。
11)考慮使用Application Context而不是Activity Context
對(duì)于大部分非必須使用Activity Context的情況(Dialog的Context就必須是Activity Context)东囚,我們都可以考慮使用Application Context而不是Activity的Context,這樣可以避免不經(jīng)意的Activity泄露顾瞪。
12)注意臨時(shí)Bitmap對(duì)象的及時(shí)回收
雖然在大多數(shù)情況下舔庶,我們會(huì)對(duì)Bitmap增加緩存機(jī)制,但是在某些時(shí)候陈醒,部分Bitmap是需要及時(shí)回收的惕橙。例如臨時(shí)創(chuàng)建的某個(gè)相對(duì)比較大的bitmap對(duì)象,在經(jīng)過(guò)變換得到新的bitmap對(duì)象之后钉跷,應(yīng)該盡快回收原始的bitmap弥鹦,這樣能夠更快釋放原始bitmap所占用的空間。
需要特別留意的是Bitmap類(lèi)里面提供的createBitmap()方法:
這個(gè)函數(shù)返回的bitmap有可能和source bitmap是同一個(gè)爷辙,在回收的時(shí)候彬坏,需要特別檢查source bitmap與return bitmap的引用是否相同,只有在不等的情況下膝晾,才能夠執(zhí)行source bitmap的recycle方法栓始。
13)注意WebView的泄漏
Android中的WebView存在很大的兼容性問(wèn)題,不僅僅是Android系統(tǒng)版本的不同對(duì)WebView產(chǎn)生很大的差異血当,另外不同的廠商出貨的ROM里面WebView也存在著很大的差異幻赚。更嚴(yán)重的是標(biāo)準(zhǔn)的WebView存在內(nèi)存泄露的問(wèn)題,看這里WebView causes memory leak - leaks the parent Activity臊旭。所以通常根治這個(gè)問(wèn)題的辦法是為WebView開(kāi)啟另外一個(gè)進(jìn)程落恼,通過(guò)AIDL與主進(jìn)程進(jìn)行通信,WebView所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時(shí)機(jī)進(jìn)行銷(xiāo)毀离熏,從而達(dá)到內(nèi)存的完整釋放佳谦。
14)資源文件需要選擇合適的文件夾進(jìn)行存放
我們知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夾下的圖片在不同的設(shè)備上會(huì)經(jīng)過(guò)scale的處理。例如我們只在hdpi的目錄下放置了一張100100的圖片滋戳,那么根據(jù)換算關(guān)系钻蔑,xxhdpi的手機(jī)去引用那張圖片就會(huì)被拉伸到200200啥刻。需要注意到在這種情況下,內(nèi)存占用是會(huì)顯著提高的矢棚。對(duì)于不希望被拉伸的圖片郑什,需要放到assets或者nodpi的目錄下。
15)謹(jǐn)慎使用static對(duì)象
因?yàn)閟tatic的生命周期過(guò)長(zhǎng)蒲肋,和應(yīng)用的進(jìn)程保持一致蘑拯,使用不當(dāng)很可能導(dǎo)致對(duì)象泄漏,在Android中應(yīng)該謹(jǐn)慎使用static對(duì)象兜粘。
16)特別留意單例對(duì)象中不合理的持有
雖然單例模式簡(jiǎn)單實(shí)用申窘,提供了很多便利性,但是因?yàn)閱卫纳芷诤蛻?yīng)用保持一致孔轴,使用不合理很容易出現(xiàn)持有對(duì)象的泄漏剃法。
17)珍惜Services資源
18)優(yōu)化布局層次,減少內(nèi)存消耗
越扁平化的視圖布局路鹰,占用的內(nèi)存就越少贷洲,效率越高。我們需要盡量保證布局足夠扁平化晋柱,當(dāng)使用系統(tǒng)提供的View無(wú)法實(shí)現(xiàn)足夠扁平的時(shí)候考慮使用自定義View來(lái)達(dá)到目的优构。
19)謹(jǐn)慎使用“抽象”編程
很多時(shí)候,開(kāi)發(fā)者會(huì)使用抽象類(lèi)作為”好的編程實(shí)踐”雁竞,因?yàn)槌橄竽軌蛱嵘a的靈活性與可維護(hù)性钦椭。然而,抽象會(huì)導(dǎo)致一個(gè)顯著的額外內(nèi)存開(kāi)銷(xiāo):他們需要同等量的代碼用于可執(zhí)行碑诉,那些代碼會(huì)被mapping到內(nèi)存中彪腔,因此如果你的抽象沒(méi)有顯著的提升效率,應(yīng)該盡量避免他們进栽。
20)謹(jǐn)慎使用多進(jìn)程
使用多進(jìn)程可以把應(yīng)用中的部分組件運(yùn)行在單獨(dú)的進(jìn)程當(dāng)中德挣,這樣可以擴(kuò)大應(yīng)用的內(nèi)存占用范圍,但是這個(gè)技術(shù)必須謹(jǐn)慎使用快毛,絕大多數(shù)應(yīng)用都不應(yīng)該貿(mào)然使用多進(jìn)程盲厌,一方面是因?yàn)槭褂枚噙M(jìn)程會(huì)使得代碼邏輯更加復(fù)雜,另外如果使用不當(dāng)祸泪,它可能反而會(huì)導(dǎo)致顯著增加內(nèi)存。當(dāng)你的應(yīng)用需要運(yùn)行一個(gè)常駐后臺(tái)的任務(wù)建芙,而且這個(gè)任務(wù)并不輕量没隘,可以考慮使用這個(gè)技術(shù)。
一個(gè)典型的例子是創(chuàng)建一個(gè)可以長(zhǎng)時(shí)間后臺(tái)播放的Music Player禁荸。如果整個(gè)應(yīng)用都運(yùn)行在一個(gè)進(jìn)程中右蒲,當(dāng)后臺(tái)播放的時(shí)候阀湿,前臺(tái)的那些UI資源也沒(méi)有辦法得到釋放。類(lèi)似這樣的應(yīng)用可以切分成2個(gè)進(jìn)程:一個(gè)用來(lái)操作UI瑰妄,另外一個(gè)給后臺(tái)的Service陷嘴。
做好內(nèi)存優(yōu)化是一項(xiàng)長(zhǎng)期的工作, 需要在很多地方注意间坐,且行且珍惜灾挨!