Memory Churn內(nèi)存抖動(dòng),內(nèi)存抖動(dòng)是因?yàn)樵诙虝r(shí)間內(nèi)大量的對(duì)象被創(chuàng)建又馬上被釋放。瞬間產(chǎn)生大量的對(duì)象會(huì)嚴(yán)重占用Young Generation的內(nèi)存區(qū)域,當(dāng)達(dá)到閥值,剩余空間不夠的時(shí)候,會(huì)觸發(fā)GC從而導(dǎo)致剛產(chǎn)生的對(duì)象又很快被回收。即使每次分配的對(duì)象占用了很少的內(nèi)存,但是他們疊加在一起會(huì)增加Heap的壓力,從而觸發(fā)更多其他類型的GC。這個(gè)操作有可能會(huì)影響到幀率,并使得用戶感知到性能問(wèn)題
1.減少對(duì)象的內(nèi)存占用
1.1使用更加輕量的數(shù)據(jù)結(jié)構(gòu)
可以考慮使用ArrayMap/SparseArray而不是HashMap等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu)。圖8演示了HashMap的簡(jiǎn)要工作原理,相比起
Android專門(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的自動(dòng)裝箱(autoboxing),并且避免了裝箱后的解箱。
需要了解HashMap的實(shí)現(xiàn)原理
1.2避免在android里面使用Enum
Android官方培訓(xùn)課程提到過(guò)“Enums often require more than twice as much memory as static constants. You should
strictly avoid using enums on Android.”,具體原理請(qǐng)參考《Android性能優(yōu)化典范(三)》,所以請(qǐng)避免在Android里面使用
到枚舉。
1.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,存在很大差異。
1.4使用更小的圖片
在涉及給到資源圖片時(shí),我們需要特別留意這張圖片是否存在可以壓縮的空間,是否可以使用更小的圖片。盡量使用更小的
圖片不僅可以減少內(nèi)存的使用,還能避免出現(xiàn)大量的InflationException。假設(shè)有一張很大的圖片被XML文件直接引用,很有
可能在初始化視圖時(shí)會(huì)因?yàn)閮?nèi)存不足而發(fā)生InflationException,這個(gè)問(wèn)題的根本原因其實(shí)是發(fā)生了OOM。
2.內(nèi)存對(duì)象的重復(fù)利用
大多數(shù)對(duì)象的復(fù)用,最終實(shí)施的方案都是利用對(duì)象池技術(shù),要么是在編寫(xiě)代碼時(shí)顯式地在程序里創(chuàng)建對(duì)象池,然后處理好復(fù)
用的實(shí)現(xiàn)邏輯。要么就是利用系統(tǒng)框架既有的某些復(fù)用特性,減少對(duì)象的重復(fù)創(chuàng)建,從而降低內(nèi)存的分配與回收在Android上
面最常用的一個(gè)緩存算法是LRU(Least Recently Use)
2.1 復(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)去
2.2 注意在ListView/GridView等出現(xiàn)大量重復(fù)子組件的視圖里對(duì)ConvertView的復(fù)用
2.3 Bitmap對(duì)象的復(fù)用
在ListView與GridView等顯示大量圖片的控件里,需要使用LRU的機(jī)制來(lái)緩存處理好的Bitmap
2.3.1 利用inBitmap的高級(jí)特性提高Android系統(tǒng)在Bitmap分配與釋放執(zhí)行效率
使用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)存大小使用inBitmap需要注意幾個(gè)限制條件:
在SDK 11 -> 18之間,重用的Bitmap大小必須是一致的。例如給inBitmap賦值的圖片大小為100-100,那么新申請(qǐng)的Bitmap
必須也為100-100才能夠被重用。從SDK 19開(kāi)始,新申請(qǐng)的Bitmap大小必須小于或者等于已經(jīng)賦值過(guò)的Bitmap大小。
新申請(qǐng)的Bitmap與舊的Bitmap必須有相同的解碼格式。例如大家都是8888的,如果前面的Bitmap是8888,那么就不能支持
4444與565格式的Bitmap了。我們可以創(chuàng)建一個(gè)包含多種典型可重用Bitmap的對(duì)象池,這樣后續(xù)的Bitmap創(chuàng)建都能夠找到合
適的“模板”去進(jìn)行重用
另外,在2.x的系統(tǒng)上,盡管Bitmap是分配在Native層,但還是無(wú)法避免被計(jì)算到OOM的引用計(jì)數(shù)器里。這里提示一下,不
少應(yīng)用會(huì)通過(guò)反射vBitmapFactory.Options里面的inNativeAlloc來(lái)達(dá)到擴(kuò)大使用內(nèi)存的目的,但是如果大家都這么做,對(duì)系統(tǒng)
整體會(huì)造成一定的負(fù)面影響,建議謹(jǐn)慎采納。
2.4 避免在onDraw方法里面執(zhí)行對(duì)象的創(chuàng)建
類似onDraw等頻繁調(diào)用的方法,一定需要注意避免在這里做創(chuàng)建對(duì)象的操作,因?yàn)樗麜?huì)迅速增加內(nèi)存的使用,而且很容易引
起頻繁的gc,甚至是內(nèi)存抖動(dòng)
2.5 StringBuilder
在有些時(shí)候,代碼中會(huì)需要使用到大量的字符串拼接的操作,這種時(shí)候有必要考慮使用StringBuilder來(lái)替代頻繁的“+”。
3.避免對(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)題
最新的LeakCanary開(kāi)源控件,可以很好的幫助我們發(fā)現(xiàn)內(nèi)存泄露的情況,更多關(guān)于LeakCanary的介紹,請(qǐng)看 這里( 中文使
用說(shuō)明)。另外也可以使用傳統(tǒng)的MAT工具查找內(nèi)存泄露
3.1 注意Activity的泄露
通常來(lái)說(shuō),Activity的泄漏是內(nèi)存泄漏里面最嚴(yán)重的問(wèn)題,它占用的內(nèi)存多,影響面廣,我們需要特別注意以下兩種情況導(dǎo)致
的Activity泄漏:
(1)內(nèi)部類引用導(dǎo)致Activity的泄漏
最典型的場(chǎng)景是Handler導(dǎo)致的Activity泄漏,如果Handler中有延遲的任務(wù)或者是等待執(zhí)行的任務(wù)隊(duì)列過(guò)長(zhǎng),都有可能因?yàn)?Handler繼續(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)系的目的。
(2)Activity Context被傳遞到其他實(shí)例中,這可能導(dǎo)致自身被引用而發(fā)生泄漏。
內(nèi)部類引起的泄漏不僅僅會(huì)發(fā)生在Activity上,其他任何內(nèi)部類出現(xiàn)的地方,都需要特別留意!我們可以考慮盡量使用static類
型的內(nèi)部類,同時(shí)使用WeakReference的機(jī)制來(lái)避免因?yàn)榛ハ嘁枚霈F(xiàn)的泄露
(3)注意臨時(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所占用的空間
(4)注意監(jiān)聽(tīng)器的注銷(xiāo)
在Android程序里面存在很多需要register與unregister的監(jiān)聽(tīng)器,我們需要確保在合適的時(shí)候及時(shí)unregister那些監(jiān)聽(tīng)器。自
己手動(dòng)add的listener,需要記得及時(shí)remove這個(gè)listener
(5)注意緩存容器中的對(duì)象泄漏
有時(shí)候,我們?yōu)榱颂岣邔?duì)象的復(fù)用性把某些對(duì)象放到緩存容器中,可是如果這些對(duì)象沒(méi)有及時(shí)從容器中清除,也是有可能導(dǎo)
致內(nèi)存泄漏的。例如,針對(duì)2.3的系統(tǒng),如果把drawable添加到緩存容器,因?yàn)閐rawable與View的強(qiáng)應(yīng)用,很容易導(dǎo)activity
發(fā)生泄漏。而從4.0開(kāi)始,就不存在這個(gè)問(wèn)題。解決這個(gè)問(wèn)題,需要對(duì)2.3系統(tǒng)上的緩存drawable做特殊封裝,處理引用解綁
的問(wèn)題,避免泄漏的情況
(6)注意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)題,請(qǐng)看 這里。所以通常
根治這個(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)存的完整釋放
(7)注意Cursor對(duì)象是否及時(shí)關(guān)閉
在程序中我們經(jīng)常會(huì)進(jìn)行查詢數(shù)據(jù)庫(kù)的操作,但時(shí)常會(huì)存在不小心使用Cursor之后沒(méi)有及時(shí)關(guān)閉的情況。這些Cursor的泄
露,反復(fù)多次出現(xiàn)的話會(huì)對(duì)內(nèi)存管理產(chǎn)生很大的負(fù)面影響,我們需要謹(jǐn)記對(duì)Cursor對(duì)象的及時(shí)關(guān)閉
4.內(nèi)存使用策略優(yōu)化
4.1 謹(jǐn)慎使用large Heap
正如前面提到的,Android設(shè)備根據(jù)硬件與軟件的設(shè)置差異而存在不同大小的內(nèi)存空間,他們?yōu)閼?yīng)用程序設(shè)置了不同大小的
Heap限制閾值。你可以通過(guò)調(diào)用getMemoryClass()來(lái)獲取應(yīng)用的可用Heap大小。在一些特殊的情景下,你可以通過(guò)在
manifest的application標(biāo)簽下添加largeHeap=true的屬性來(lái)為應(yīng)用聲明一個(gè)更大的heap空間。然后,你可以通過(guò)
getLargeMemoryClass()來(lái)獲取到這個(gè)更大的heap size閾值。然而,聲明得到更大Heap閾值的本意是為了一小部分會(huì)消耗大
量RAM的應(yīng)用(例如一個(gè)大圖片的編輯應(yīng)用)。不要輕易的因?yàn)槟阈枰褂酶嗟膬?nèi)存而去請(qǐng)求一個(gè)大的Heap Size。只有當(dāng)你
清楚的知道哪里會(huì)使用大量的內(nèi)存并且知道為什么這些內(nèi)存必須被保留時(shí)才去使用large heap。因此請(qǐng)謹(jǐn)慎使用large heap屬
性。使用額外的內(nèi)存空間會(huì)影響系統(tǒng)整體的用戶體驗(yàn),并且會(huì)使得每次gc的運(yùn)行時(shí)間更長(zhǎng)。在任務(wù)切換時(shí),系統(tǒng)的性能會(huì)大
打折扣。另外, large heap并不一定能夠獲取到更大的heap。在某些有嚴(yán)格限制的機(jī)器上,large heap的大小和通常的heap
size是一樣的。因此即使你申請(qǐng)了large heap,你還是應(yīng)該通過(guò)執(zhí)行g(shù)etMemoryClass()來(lái)檢查實(shí)際獲取到的heap大小。
4.2 綜合考慮設(shè)備內(nèi)存閾值與其他因素設(shè)計(jì)合適的緩存大小
例如,在設(shè)計(jì)ListView或者GridView的Bitmap LRU緩存的時(shí)候,需要考慮的點(diǎn)有:
(1)應(yīng)用程序剩下了多少可用的內(nèi)存空間?
(2)有多少圖片會(huì)被一次呈現(xiàn)到屏幕上?有多少圖片需要事先緩存好以便快速滑動(dòng)時(shí)能夠立即顯示到屏幕?
(3)設(shè)備的屏幕大小與密度是多少? 一個(gè)xhdpi的設(shè)備會(huì)比hdpi需要一個(gè)更大的Cache來(lái)hold住同樣數(shù)量的圖片。
(4)不同的頁(yè)面針對(duì)Bitmap的設(shè)計(jì)的尺寸與配置是什么,大概會(huì)花費(fèi)多少內(nèi)存?
(5)頁(yè)面圖片被訪問(wèn)的頻率?是否存在其中的一部分比其他的圖片具有更高的訪問(wèn)頻繁?如果是,也許你想要保存那些最常訪
問(wèn)的到內(nèi)存中,或者為不同組別的位圖(按訪問(wèn)頻率分組)設(shè)置多個(gè)LruCache容器。
4.3 onLowMemory()與onTrimMemory()
Android用戶可以隨意在不同的應(yīng)用之間進(jìn)行快速切換。為了讓background的應(yīng)用能夠迅速的切換到forground,每一個(gè)
background的應(yīng)用都會(huì)占用一定的內(nèi)存。Android系統(tǒng)會(huì)根據(jù)當(dāng)前的系統(tǒng)的內(nèi)存使用情況,決定回收部分background的應(yīng)用
內(nèi)存。如果background的應(yīng)用從暫停狀態(tài)直接被恢復(fù)到forground,能夠獲得較快的恢復(fù)體驗(yàn),如果background應(yīng)用是從Kill
的狀態(tài)進(jìn)行恢復(fù),相比之下就顯得稍微有點(diǎn)慢
onLowMemory():Android系統(tǒng)提供了一些回調(diào)來(lái)通知當(dāng)前應(yīng)用的內(nèi)存使用情況,通常來(lái)說(shuō),當(dāng)所有的background應(yīng)用都被
kill掉的時(shí)候,forground應(yīng)用會(huì)收到onLowMemory()的回調(diào)。在這種情況下,需要盡快釋放當(dāng)前應(yīng)用的非必須的內(nèi)存資源,
從而確保系統(tǒng)能夠繼續(xù)穩(wěn)定運(yùn)行
onTrimMemory(int):Android系統(tǒng)從4.0開(kāi)始還提供了onTrimMemory()的回調(diào),當(dāng)系統(tǒng)內(nèi)存達(dá)到某些條件的時(shí)候,所有正在
運(yùn)行的應(yīng)用都會(huì)收到這個(gè)回調(diào),同時(shí)在這個(gè)回調(diào)里面會(huì)傳遞以下的參數(shù),代表不同的內(nèi)存使用情況,收到onTrimMemory()回
調(diào)的時(shí)候,需要根據(jù)傳遞的參數(shù)類型進(jìn)行判斷,合理的選擇釋放自身的一些內(nèi)存占用,一方面可以提高系統(tǒng)的整體運(yùn)行流暢
度,另外也可以避免自己被系統(tǒng)判斷為優(yōu)先需要?dú)⒌舻膽?yīng)用。
TRIM_MEMORY_UI_HIDDEN:你的應(yīng)用程序的所有UI界面被隱藏了,即用戶點(diǎn)擊了Home鍵或者Back鍵退出應(yīng)用,導(dǎo)致應(yīng)
用的UI界面完全不可見(jiàn)。這個(gè)時(shí)候應(yīng)該釋放一些不可見(jiàn)的時(shí)候非必須的資源
當(dāng)程序正在前臺(tái)運(yùn)行的時(shí)候,可能會(huì)接收到從onTrimMemory()中返回的下面的值之一:
TRIM_MEMORY_RUNNING_MODERATE:你的應(yīng)用正在運(yùn)行并且不會(huì)被列為可殺死的。但是設(shè)備此時(shí)正運(yùn)行于低內(nèi)存狀
態(tài)下,系統(tǒng)開(kāi)始觸發(fā)殺死LRU Cache中的Process的機(jī)制。
TRIM_MEMORY_RUNNING_LOW:你的應(yīng)用正在運(yùn)行且沒(méi)有被列為可殺死的。但是設(shè)備正運(yùn)行于更低內(nèi)存的狀態(tài)下,你應(yīng)
該釋放不用的資源用來(lái)提升系統(tǒng)性能。
TRIM_MEMORY_RUNNING_CRITICAL:你的應(yīng)用仍在運(yùn)行,但是系統(tǒng)已經(jīng)把LRU Cache中的大多數(shù)進(jìn)程都已經(jīng)殺死,因
此你應(yīng)該立即釋放所有非必須的資源。如果系統(tǒng)不能回收到足夠的RAM數(shù)量,系統(tǒng)將會(huì)清除所有的LRU緩存中的進(jìn)程,并且
開(kāi)始?xì)⑺滥切┲氨徽J(rèn)為不應(yīng)該殺死的進(jìn)程,例如那個(gè)包含了一個(gè)運(yùn)行態(tài)Service的進(jìn)程。
當(dāng)應(yīng)用進(jìn)程退到后臺(tái)正在被Cached的時(shí)候,可能會(huì)接收到從onTrimMemory()中返回的下面的值之一:
TRIM_MEMORY_BACKGROUND: 系統(tǒng)正運(yùn)行于低內(nèi)存狀態(tài)并且你的進(jìn)程正處于LRU緩存名單中最不容易殺掉的位置。盡
管你的應(yīng)用進(jìn)程并不是處于被殺掉的高危險(xiǎn)狀態(tài),系統(tǒng)可能已經(jīng)開(kāi)始?xì)⒌鬖RU緩存中的其他進(jìn)程了。你應(yīng)該釋放那些容易恢
復(fù)的資源,以便于你的進(jìn)程可以保留下來(lái),這樣當(dāng)用戶回退到你的應(yīng)用的時(shí)候才能夠迅速恢復(fù)。
TRIM_MEMORY_MODERATE: 系統(tǒng)正運(yùn)行于低內(nèi)存狀態(tài)并且你的進(jìn)程已經(jīng)已經(jīng)接近LRU名單的中部位置。如果系統(tǒng)開(kāi)始變
得更加內(nèi)存緊張,你的進(jìn)程是有可能被殺死的。
TRIM_MEMORY_COMPLETE: 系統(tǒng)正運(yùn)行于低內(nèi)存的狀態(tài)并且你的進(jìn)程正處于LRU名單中最容易被殺掉的位置。你應(yīng)該釋
放任何不影響你的應(yīng)用恢復(fù)狀態(tài)的資源。
因?yàn)閛nTrimMemory()的回調(diào)是在API 14才被加進(jìn)來(lái)的,對(duì)于老的版本,你可以使用onLowMemory)回調(diào)來(lái)進(jìn)行兼容。
onLowMemory相當(dāng)與TRIM_MEMORY_COMPLETE。
請(qǐng)注意:當(dāng)系統(tǒng)開(kāi)始清除LRU緩存中的進(jìn)程時(shí),雖然它首先按照LRU的順序來(lái)執(zhí)行操作,但是它同樣會(huì)考慮進(jìn)程的內(nèi)存使用
量以及其他因素。占用越少的進(jìn)程越容易被留下來(lái)。
4.4 資源文件需要選擇合適的文件夾進(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的目錄下
4.5 Try catch某些大內(nèi)存分配的操作
在某些情況下,我們需要事先評(píng)估那些可能發(fā)生OOM的代碼,對(duì)于這些可能發(fā)生OOM的代碼,加入catch機(jī)制,可以考慮在
catch里面嘗試一次降級(jí)的內(nèi)存分配操作。例如decode bitmap的時(shí)候,catch到OOM,可以嘗試把采樣比例再增加一倍之
后,再次嘗試decode
4.6 謹(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ì)象
4.7 特別留意單例對(duì)象中不合理的持有
雖然單例模式簡(jiǎn)單實(shí)用,提供了很多便利性,但是因?yàn)閱卫纳芷诤蛻?yīng)用保持一致,使用不合理很容易出現(xiàn)持有對(duì)象的
泄漏。
4.8 珍惜Service資源
如果你的應(yīng)用需要在后臺(tái)使用service,除非它被觸發(fā)并執(zhí)行一個(gè)任務(wù),否則其他時(shí)候Service都應(yīng)該是停止?fàn)顟B(tài)。另外需要注
意當(dāng)這個(gè)service完成任務(wù)之后因?yàn)橥V箂ervice失敗而引起的內(nèi)存泄漏。 當(dāng)你啟動(dòng)一個(gè)Service,系統(tǒng)會(huì)傾向?yàn)榱吮A暨@個(gè)
Service而一直保留Service所在的進(jìn)程。這使得進(jìn)程的運(yùn)行代價(jià)很高,因?yàn)橄到y(tǒng)沒(méi)有辦法把Service所占用的RAM空間騰出來(lái)
讓給其他組件,另外Service還不能被Paged out。這減少了系統(tǒng)能夠存放到LRU緩存當(dāng)中的進(jìn)程數(shù)量,它會(huì)影響應(yīng)用之間的
切換效率,甚至?xí)?dǎo)致系統(tǒng)內(nèi)存使用不穩(wěn)定,從而無(wú)法繼續(xù)保持住所有目前正在運(yùn)行的service。 建議使用IntentService,它
會(huì)在處理完交代給它的任務(wù)之后盡快結(jié)束自己
4.9優(yōu)化布局層次,減少內(nèi)存消耗
越扁平化的視圖布局,占用的內(nèi)存就越少,效率越高。我們需要盡量保證布局足夠扁平化,當(dāng)使用系統(tǒng)提供的View無(wú)法實(shí)現(xiàn)
足夠扁平的時(shí)候考慮使用自定義View來(lái)達(dá)到目的
4.10 謹(jǐn)慎使用“抽象”編程
很多時(shí)候,開(kāi)發(fā)者會(huì)使用抽象類作為”好的編程實(shí)踐”,因?yàn)槌橄竽軌蛱嵘a的靈活性與可維護(hù)性。然而,抽象會(huì)導(dǎo)致一個(gè)
顯著的額外內(nèi)存開(kāi)銷(xiāo):他們需要同等量的代碼用于可執(zhí)行,那些代碼會(huì)被mapping到內(nèi)存中,因此如果你的抽象沒(méi)有顯著的
提升效率,應(yīng)該盡量避免他們
4.11 使用nano protobufs序列化數(shù)據(jù)
Protocol buffers是由Google為序列化結(jié)構(gòu)數(shù)據(jù)而設(shè)計(jì)的,一種語(yǔ)言無(wú)關(guān),平臺(tái)無(wú)關(guān),具有良好的擴(kuò)展性。類似XML,卻比
XML更加輕量,快速,簡(jiǎn)單。如果你需要為你的數(shù)據(jù)實(shí)現(xiàn)序列化與協(xié)議化,建議使用nano protobufs
4.12 謹(jǐn)慎使用依賴注入框架
4.13 謹(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)有辦法得到釋放。類似這樣的應(yīng)用可以切分成2個(gè)進(jìn)程:一個(gè)用來(lái)操作UI,另外一個(gè)給后臺(tái)的Service
4.14 使用ProGuard來(lái)剔除不需要的代碼
ProGuard能夠通過(guò)移除不需要的代碼,重命名類,域與方法等等對(duì)代碼進(jìn)行壓縮,優(yōu)化與混淆。使用ProGuard可以使得你
的代碼更加緊湊,這樣能夠減少mapping代碼所需要的內(nèi)存空間
4.15 謹(jǐn)慎使用第三方libraries
很多開(kāi)源的library代碼都不是為移動(dòng)網(wǎng)絡(luò)環(huán)境而編寫(xiě)的,如果運(yùn)用在移動(dòng)設(shè)備上,并不一定適合。即使是針對(duì)Android而設(shè)計(jì)
的library,也需要特別謹(jǐn)慎,特別是在你不知道引入的library具體做了什么事情的時(shí)候。例如,其中一個(gè)library使用的是nano
protobufs, 而另外一個(gè)使用的是micro protobufs。這樣一來(lái),在你的應(yīng)用里面就有2種protobuf的實(shí)現(xiàn)方式。這樣類似的沖突
還可能發(fā)生在輸出日志,加載圖片,緩存等等模塊里面。另外不要為了1個(gè)或者2個(gè)功能而導(dǎo)入整個(gè)library,如果沒(méi)有一個(gè)合
適的庫(kù)與你的需求相吻合,你應(yīng)該考慮自己去實(shí)現(xiàn),而不是導(dǎo)入一個(gè)大而全的解決方案。
4.16 考慮不同的實(shí)現(xiàn)方式來(lái)優(yōu)化內(nèi)存占用
使用自定義View技術(shù)實(shí)現(xiàn)比切圖更省內(nèi)存
總結(jié):
設(shè)計(jì)風(fēng)格很大程度上會(huì)影響到程序的內(nèi)存與性能,相對(duì)來(lái)說(shuō),如果大量使用類似Material Design的風(fēng)格,不僅安裝包可以變
小,還可以減少內(nèi)存的占用,渲染性能與加載性能都會(huì)有一定的提升。
內(nèi)存優(yōu)化并不就是說(shuō)程序占用的內(nèi)存越少就越好,如果因?yàn)橄胍3指偷膬?nèi)存占用,而頻繁觸發(fā)執(zhí)行g(shù)c操作,在某種程度
上反而會(huì)導(dǎo)致應(yīng)用性能整體有所下降,這里需要綜合考慮做一定的權(quán)衡。
Android的內(nèi)存優(yōu)化涉及的知識(shí)面還有很多:內(nèi)存管理的細(xì)節(jié),垃圾回收的工作原理,如何查找內(nèi)存泄漏等等都可以展開(kāi)講很
多。OOM是內(nèi)存優(yōu)化當(dāng)中比較突出的一點(diǎn),盡量減少OOM的概率對(duì)內(nèi)存優(yōu)化有著很大的意義