Android性能優(yōu)化
用戶體驗(yàn)在Android開發(fā)中格外重要,一款操作卡頓、耗電量大、響應(yīng)速度慢的軟件,必然會損失大量用戶。 本文的主題就是Android性能優(yōu)化,幫你實(shí)現(xiàn)一款流暢、穩(wěn)定、耗能少的軟件。
內(nèi)存優(yōu)化并不就是說程序占用的內(nèi)存越少就越好,如果因?yàn)橄胍3指偷膬?nèi)存占用,而頻繁觸發(fā)執(zhí)行GC操作,在某種程度上反而會導(dǎo)致應(yīng)用性能整體有所下降,這里需要綜合考慮做一定的權(quán)衡。
Android的內(nèi)存優(yōu)化涉及的知識面還有很多:內(nèi)存管理的細(xì)節(jié),垃圾回收的工作原理,如何查找內(nèi)存泄漏等等。
接下來將從OOM避免和布局優(yōu)化,通過減少對象的內(nèi)存占用、內(nèi)存對象的重復(fù)利用、避免對象的內(nèi)存泄漏、內(nèi)存使用策略優(yōu)化等方面來介紹Android內(nèi)存優(yōu)化相關(guān)知識。
本文內(nèi)容是對胡凱大神Android性能優(yōu)化典范系列文章的整理,感謝大神分享!原文地址:Android性能優(yōu)化典范
Android內(nèi)存優(yōu)化之OOM
內(nèi)存溢出OOM是指應(yīng)用系統(tǒng)中存在無法回收的內(nèi)存或使用的內(nèi)存過多,最終使得程序運(yùn)行要用到的內(nèi)存大于虛擬機(jī)能提供的最大內(nèi)存。
引起內(nèi)存溢出的原因有很多種,常見的有以下幾種:
- 內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù);
- 集合類中有對對象的引用,使用完后未清空,使得JVM不能回收;
- 代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復(fù)的對象實(shí)體;
- 使用的第三方軟件中的BUG;
- 啟動參數(shù)內(nèi)存值設(shè)定的過??;
如何避免OOM總結(jié)
前面介紹了一些基礎(chǔ)的內(nèi)存管理機(jī)制以及OOM的基礎(chǔ)知識,那么在實(shí)踐操作當(dāng)中,有哪些指導(dǎo)性的規(guī)則可以參考呢?歸納下來,可以從四個方面著手,首先是減小對象的內(nèi)存占用,其次是內(nèi)存對象的重復(fù)利用,然后是避免對象的內(nèi)存泄露,最后是內(nèi)存使用策略優(yōu)化。
減小對象的內(nèi)存占用
避免OOM的第一步就是要盡量減少新分配出來的對象占用內(nèi)存的大小,盡量使用更加輕量的對象。
使用更加輕量的數(shù)據(jù)結(jié)構(gòu)
考慮使用ArrayMap/SparseArray而不是HashMap等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu),HashMap相比起Android系統(tǒng)專門為移動操作系統(tǒng)編寫的ArrayMap與SpareArray容器,在大多數(shù)情況下,效率相對低下,且更占內(nèi)存。通常的HashMap的實(shí)現(xiàn)方式更加消耗內(nèi)存,因?yàn)樗枰粋€額外的實(shí)例對象來記錄Mapping操作。另外,SparseArray更加高效在于他們避免了對key與value的自動裝箱,避免了裝箱后的解箱并且內(nèi)部數(shù)據(jù)結(jié)構(gòu)不依賴額外實(shí)體對象。
避免在Android里面使用Enum
Enum中的每一個值都是一個Object,它的每個聲明都會占用運(yùn)行時的部分內(nèi)存以便能夠引用到這個Object。因此Enum的值會比對應(yīng)的Integer和String所占用的內(nèi)存多。
定義三個靜態(tài)常量:
public static final int RED =0;
public static final int GREEN =1;
public static final int BLACK =2;
如果使用Enum表示:
public static enum Color {
RED, GREEN, BLACK
}
反編譯相關(guān)字節(jié)碼文件可以將枚舉操作過程視為如下過程
public final class Color extends java.lang.Enum<Color> {
public static final Color RED;
public static final Color GREEN;
public static final Color BLACK;
static {
RED = new Color("RED", 0);
GREEN = new Color("GREEN", 1);
BLACK = new Color("BLACK", 2);
VALUES = new Color[]{RED, GREEN, BLACK};
}
public static Color[] values() {
Color tmp = new Color[VALUES.length];
system.arraycopy(VALUES, 0, tmp, 0, VALUES.length);
return tmp;
}
public static Color valueOf(String name) {
return Enum.valueOf(name);
}
}
在Android 2.2之前的版本中,存在著關(guān)于Enum引起的性能問題,這個問題在JIT編譯器中解決了。添加一個Enum將會增大最終的DEX文件(Integer常量的13倍大)。并且會引起運(yùn)行時的過度開銷,你的應(yīng)用也會占用更多的空間。因此過度在Android開發(fā)中使用Enum將會增大DEX大小,并會增大運(yùn)行時的內(nèi)存分配大小。如果你的應(yīng)用是使用了許多的Enum,那么使用Integer或者String常量會更好。
一個枚舉可以為您的應(yīng)用程序的classes.dex文件添加大約1.0到1.4 KB的大小 。這些添加可以快速累積到復(fù)雜系統(tǒng)或共享庫。如果可能,請考慮使用@IntDef注釋,這種類型轉(zhuǎn)換保留了枚舉的所有類型安全優(yōu)勢。
關(guān)于枚舉內(nèi)存占用詳細(xì)內(nèi)容請參考【內(nèi)存優(yōu)化】避免使用Enum
減小Bitmap對象的內(nèi)存占用
Bitmap是一個極容易消耗內(nèi)存的大胖子,減小創(chuàng)建出來的Bitmap的內(nèi)存占用是很重要的,一般來說圖片的內(nèi)存占用與圖片寬高、圖片編碼格式、圖片存放目錄及手機(jī)屏幕像素密度等因素有關(guān)。常見的解決方案就是對Bitmap進(jìn)行壓縮,而Bitmap壓縮策略主要分為質(zhì)量壓縮(不改變圖片尺寸,從圖片分辨率角度入手,選用合適的編碼格式)和尺寸壓縮兩種。通常來說有下面2個措施:
- inSampleSize:縮放比例,在把圖片載入內(nèi)存之前,我們需要先計(jì)算出一個合適的縮放比例,避免不必要的大圖載入。
- Decode Format:解碼格式,選擇ARGB-8888、RBG-565、ARGB-4444、ALPHA-8,存在很大差異,比如:ARGB-8888格式的圖片,每像素占用 4 Byte,而 RGB-565則是 2 Byte。
使用更小的圖片
在設(shè)計(jì)給到資源圖片的時候,我們需要特別留意這張圖片是否存在可以壓縮的空間,是否可以使用一張更小的圖片。盡量使用更小的圖片不僅僅可以減少內(nèi)存的使用,還可以避免出現(xiàn)大量的InflationException。假設(shè)有一張很大的圖片被XML文件直接引用,很有可能在初始化視圖的時候就會因?yàn)閮?nèi)存不足而發(fā)生InflationException,這個問題的根本原因其實(shí)是發(fā)生了OOM。
內(nèi)存對象的重復(fù)利用
大多數(shù)對象的復(fù)用,最終實(shí)施的方案都是利用對象池技術(shù),要么是在編寫代碼的時候顯式的在程序里面去創(chuàng)建對象池,然后處理好復(fù)用的實(shí)現(xiàn)邏輯,要么就是利用系統(tǒng)框架既有的某些復(fù)用特性達(dá)到減少對象的重復(fù)創(chuàng)建,從而減少內(nèi)存的分配與回收。在Android上面最常用的一個緩存算法是LRU算法。
在Android開發(fā)中,LruCache是基于LRU算法實(shí)現(xiàn)的。當(dāng)緩存空間使用完的情況下,最久沒被使用的對象會被清除出緩存。LruCache是一個內(nèi)存層面的緩存,如果想要進(jìn)行本地磁盤緩存,推薦使用DiskLruCache。
LruCache常用的場景是做圖片內(nèi)存緩存,電商類APP經(jīng)常會用到圖片,當(dāng)我們對圖片資源做了內(nèi)存緩存,不僅可以增強(qiáng)用戶體驗(yàn),而且可以減少圖片網(wǎng)絡(luò)請求,減少用戶流量耗費(fèi)。
LruCache內(nèi)部又維護(hù)了一個雙向鏈表結(jié)構(gòu)LinkeadHashMap。HashMap和雙向鏈表合二為一即是LinkedHashMap。所謂LinkedHashMap,其落腳點(diǎn)在HashMap,因此更準(zhǔn)確地說,它是一個將所有Entry節(jié)點(diǎn)鏈入一個雙向鏈表的HashMap。由于LinkedHashMap是HashMap的子類,所以LinkedHashMap自然會擁有HashMap的所有特性。比如,LinkedHashMap的元素存取過程基本與HashMap基本類似,只是在細(xì)節(jié)實(shí)現(xiàn)上稍有不同。
LinkedHashMap 的存取過程基本與HashMap基本類似,只是在細(xì)節(jié)實(shí)現(xiàn)上稍有不同,這是由LinkedHashMap本身的特性所決定的,因?yàn)樗?strong>額外維護(hù)一個雙向鏈表用于保持迭代順序。特別地,該迭代順序可以是插入順序,也可以是訪問順序。
LinkedHashMap實(shí)現(xiàn)LRU的必要前提是將accessOrder標(biāo)志位設(shè)為true以便開啟按訪問順序排序的模式。我們可以看到,無論是put方法還是get方法,都會導(dǎo)致目標(biāo)Entry成為最近訪問的Entry,因此就把該Entry加入到了雙向鏈表的末尾。
復(fù)用系統(tǒng)自帶的資源
Android系統(tǒng)本身內(nèi)置了很多的資源,例如字符串/顏色/圖片/動畫/樣式以及簡單布局等等,這些資源都可以在應(yīng)用程序中直接引用。這樣做不僅僅可以減少應(yīng)用程序的自身負(fù)重,減小APK的大小,另外還可以一定程度上減少內(nèi)存的開銷,復(fù)用性更好。但是也有必要留意Android系統(tǒng)的版本差異性,對那些不同系統(tǒng)版本上表現(xiàn)存在很大差異,不符合需求的情況,還是需要應(yīng)用程序自身內(nèi)置進(jìn)去。
注意在ListView/GridView等出現(xiàn)大量重復(fù)子組件的視圖里面對ConvertView的復(fù)用
Bitmap對象的復(fù)用
- 在ListView與GridView等顯示大量圖片的控件里面需要使用LRU的機(jī)制來緩存處理好的Bitmap。
避免在onDraw方法里面執(zhí)行對象的創(chuàng)建
類似onDraw等頻繁調(diào)用的方法,一定需要注意避免在這里做創(chuàng)建對象的操作,因?yàn)樗麜杆僭黾觾?nèi)存的使用,而且很容易引起頻繁的GC,甚至是內(nèi)存抖動。
StringBuilder
在有些時候,代碼中會需要使用到大量的字符串拼接的操作,這種時候有必要考慮使用StringBuilder來替代頻繁的“+”。
避免對象的內(nèi)存泄露
內(nèi)存泄漏表示的是不再用到的對象因?yàn)楸诲e誤引用而無法進(jìn)行回收。
內(nèi)存對象的泄漏,會導(dǎo)致一些不再使用的對象無法及時釋放,這樣一方面占用了寶貴的內(nèi)存空間,很容易導(dǎo)致后續(xù)需要分配內(nèi)存的時候,空閑空間不足而出現(xiàn)OOM。顯然,這還使得每級Generation的內(nèi)存區(qū)域可用空間變小,GC就會更容易被觸發(fā),容易出現(xiàn)內(nèi)存抖動,從而引起性能問題。
Android常見內(nèi)存泄漏方式
不止Android程序員,內(nèi)存泄露應(yīng)該是大部分程序員都遇到過的問題,可以說大部分的內(nèi)存問題都是內(nèi)存泄露導(dǎo)致的,Android里也有一些很常見的內(nèi)存泄露問題:
- 單例(主要原因還是因?yàn)橐话闱闆r下單例都是全局的,有時候會引用一些實(shí)際生命周期比較短的變量,導(dǎo)致其無法釋放)
- 靜態(tài)變量(同樣也是因?yàn)樯芷诒容^長)
- 非靜態(tài)內(nèi)部類造成的內(nèi)存泄漏
- Handler內(nèi)存泄露(Handler或Runnable作為非靜態(tài)內(nèi)部類)
- 匿名內(nèi)部類(匿名內(nèi)部類會引用外部類,導(dǎo)致無法釋放,比如各種回調(diào))
- 資源使用完未關(guān)閉(BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap)
注意Activity的泄漏
通常來說,Activity的泄漏是內(nèi)存泄漏里面最嚴(yán)重的問題,它占用的內(nèi)存多,影響面廣,我們需要特別注意以下兩種情況導(dǎo)致的Activity泄漏:
內(nèi)部類引用導(dǎo)致Activity的泄漏
最典型的場景是Handler導(dǎo)致的Activity泄漏,如果Handler中有延遲的任務(wù)或者是等待執(zhí)行的任務(wù)隊(duì)列過長,都有可能因?yàn)镠andler繼續(xù)執(zhí)行而導(dǎo)致Activity發(fā)生泄漏。此時的引用關(guān)系鏈?zhǔn)荓ooper -> MessageQueue -> Message -> Handler -> Activity。為了解決這個問題,可以在UI退出之前,執(zhí)行remove Handler消息隊(duì)列中的消息與runnable對象。或者是使用Static + WeakReference的方式來達(dá)到斷開Handler與Activity之間存在引用關(guān)系的目的。Activity Context被傳遞到其他實(shí)例中,這可能導(dǎo)致自身被引用而發(fā)生泄漏。
內(nèi)部類引起的泄漏不僅僅會發(fā)生在Activity上,其他任何內(nèi)部類出現(xiàn)的地方,都需要特別留意!我們可以考慮盡量使用static類型的內(nèi)部類,同時使用WeakReference的機(jī)制來避免因?yàn)榛ハ嘁枚霈F(xiàn)的泄露。
考慮使用Application Context而不是Activity Context
對于大部分非必須使用Activity Context的情況(Dialog的Context就必須是Activity Context),我們都可以考慮使用Application Context而不是Activity的Context,這樣可以避免不經(jīng)意的Activity泄露。
注意臨時Bitmap對象的及時回收
雖然在大多數(shù)情況下,我們會對Bitmap增加緩存機(jī)制,但是在某些時候,部分Bitmap是需要及時回收的。例如臨時創(chuàng)建的某個相對比較大的bitmap對象,在經(jīng)過變換得到新的bitmap對象之后,應(yīng)該盡快回收原始的bitmap,這樣能夠更快釋放原始bitmap所占用的空間。
需要特別留意的是Bitmap類里面提供的createBitmap()方法,這個函數(shù)返回的bitmap有可能和source bitmap是同一個,在回收的時候,需要特別檢查source bitmap與return bitmap的引用是否相同,只有在不等的情況下,才能夠執(zhí)行source bitmap的recycle方法。
注意監(jiān)聽器的注銷
在Android程序里面存在很多需要register與unregister的監(jiān)聽器,我們需要確保在合適的時候及時unregister那些監(jiān)聽器。自己手動add的listener,需要記得及時remove這個listener。
注意緩存容器中的對象泄漏
有時候,我們?yōu)榱颂岣邔ο蟮膹?fù)用性把某些對象放到緩存容器中,可是如果這些對象沒有及時從容器中清除,也是有可能導(dǎo)致內(nèi)存泄漏的。例如,針對2.3的系統(tǒng),如果把drawable添加到緩存容器,因?yàn)閐rawable與View的強(qiáng)應(yīng)用,很容易導(dǎo)致activity發(fā)生泄漏。而從4.0開始,就不存在這個問題。解決這個問題,需要對2.3系統(tǒng)上的緩存drawable做特殊封裝,處理引用解綁的問題,避免泄漏的情況。
注意Cursor對象是否及時關(guān)閉
在程序中我們經(jīng)常會進(jìn)行查詢數(shù)據(jù)庫的操作,但時常會存在不小心使用Cursor之后沒有及時關(guān)閉的情況。這些Cursor的泄露,反復(fù)多次出現(xiàn)的話會對內(nèi)存管理產(chǎn)生很大的負(fù)面影響,我們需要謹(jǐn)記對Cursor對象的及時關(guān)閉。
內(nèi)存使用策略優(yōu)化
謹(jǐn)慎使用large heap
正如前面提到的,Android設(shè)備根據(jù)硬件與軟件的設(shè)置差異而存在不同大小的內(nèi)存空間,他們?yōu)閼?yīng)用程序設(shè)置了不同大小的Heap限制閾值。你可以通過調(diào)用getMemoryClass()來獲取應(yīng)用的可用Heap大小。在一些特殊的情景下,你可以通過在manifest的application標(biāo)簽下添加largeHeap=true的屬性來為應(yīng)用聲明一個更大的heap空間。然后,你可以通過getLargeMemoryClass()來獲取到這個更大的heap size閾值。然而,聲明得到更大Heap閾值的本意是為了一小部分會消耗大量RAM的應(yīng)用(例如一個大圖片的編輯應(yīng)用)。不要輕易的因?yàn)槟阈枰褂酶嗟膬?nèi)存而去請求一個大的Heap Size。只有當(dāng)你清楚的知道哪里會使用大量的內(nèi)存并且知道為什么這些內(nèi)存必須被保留時才去使用large heap。因此請謹(jǐn)慎使用large heap屬性。使用額外的內(nèi)存空間會影響系統(tǒng)整體的用戶體驗(yàn),并且會使得每次gc的運(yùn)行時間更長。在任務(wù)切換時,系統(tǒng)的性能會大打折扣。另外, large heap并不一定能夠獲取到更大的heap。在某些有嚴(yán)格限制的機(jī)器上,large heap的大小和通常的heap size是一樣的。因此即使你申請了large heap,你還是應(yīng)該通過執(zhí)行getMemoryClass()來檢查實(shí)際獲取到的heap大小。
綜合考慮設(shè)備內(nèi)存閾值與其他因素設(shè)計(jì)合適的緩存大小
例如,在設(shè)計(jì)ListView或者GridView的Bitmap LRU緩存的時候,需要考慮的點(diǎn)有:
- 應(yīng)用程序剩下了多少可用的內(nèi)存空間?
- 有多少圖片會被一次呈現(xiàn)到屏幕上?有多少圖片需要事先緩存好以便快速滑動時能夠立即顯示到屏幕?
- 設(shè)備的屏幕大小與密度是多少? 一個xhdpi的設(shè)備會比hdpi需要一個更大的Cache來hold住同樣數(shù)量的圖片。
- 不同的頁面針對Bitmap的設(shè)計(jì)的尺寸與配置是什么,大概會花費(fèi)多少內(nèi)存?
- 頁面圖片被訪問的頻率?是否存在其中的一部分比其他的圖片具有更高的訪問頻繁?如果是,也許你想要保存那些最常訪問的到內(nèi)存中,或者為不同組別的位圖(按訪問頻率分組)設(shè)置多個LruCache容器。
onLowMemory()與onTrimMemory()
Android用戶可以隨意在不同的應(yīng)用之間進(jìn)行快速切換。為了讓background的應(yīng)用能夠迅速的切換到forground,每一個background的應(yīng)用都會占用一定的內(nèi)存。Android系統(tǒng)會根據(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)來通知當(dāng)前應(yīng)用的內(nèi)存使用情況,通常來說,當(dāng)所有的background應(yīng)用都被kill掉的時候,forground應(yīng)用會收到onLowMemory()的回調(diào)。在這種情況下,需要盡快釋放當(dāng)前應(yīng)用的非必須的內(nèi)存資源,從而確保系統(tǒng)能夠繼續(xù)穩(wěn)定運(yùn)行。
onTrimMemory(int):Android系統(tǒng)從4.0開始還提供了onTrimMemory()的回調(diào),當(dāng)系統(tǒng)內(nèi)存達(dá)到某些條件的時候,所有正在運(yùn)行的應(yīng)用都會收到這個回調(diào),同時在這個回調(diào)里面會傳遞以下的參數(shù),代表不同的內(nèi)存使用情況,收到onTrimMemory()回調(diào)的時候,需要根據(jù)傳遞的參數(shù)類型進(jìn)行判斷,合理的選擇釋放自身的一些內(nèi)存占用,一方面可以提高系統(tǒng)的整體運(yùn)行流暢度,另外也可以避免自己被系統(tǒng)判斷為優(yōu)先需要?dú)⒌舻膽?yīng)用。下面介紹各種不同的回調(diào)參數(shù):
TRIM_MEMORY_UI_HIDDEN:你的應(yīng)用程序的所有UI界面被隱藏了,即用戶點(diǎn)擊了Home鍵或者Back鍵退出應(yīng)用,導(dǎo)致應(yīng)用的UI界面完全不可見。這個時候應(yīng)該釋放一些不可見的時候非必須的資源
當(dāng)程序正在前臺運(yùn)行的時候,可能會接收到從onTrimMemory()中返回的下面的值之一:
- TRIM_MEMORY_RUNNING_MODERATE:你的應(yīng)用正在運(yùn)行并且不會被列為可殺死的。但是設(shè)備此時正運(yùn)行于低內(nèi)存狀態(tài)下,系統(tǒng)開始觸發(fā)殺死LRU Cache中的Process的機(jī)制。
- TRIM_MEMORY_RUNNING_LOW:你的應(yīng)用正在運(yùn)行且沒有被列為可殺死的。但是設(shè)備正運(yùn)行于更低內(nèi)存的狀態(tài)下,你應(yīng)該釋放不用的資源用來提升系統(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)將會清除所有的LRU緩存中的進(jìn)程,并且開始?xì)⑺滥切┲氨徽J(rèn)為不應(yīng)該殺死的進(jìn)程,例如那個包含了一個運(yùn)行態(tài)Service的進(jìn)程。
當(dāng)應(yīng)用進(jìn)程退到后臺正在被Cached的時候,可能會接收到從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)開始?xì)⒌鬖RU緩存中的其他進(jìn)程了。你應(yīng)該釋放那些容易恢復(fù)的資源,以便于你的進(jìn)程可以保留下來,這樣當(dāng)用戶回退到你的應(yīng)用的時候才能夠迅速恢復(fù)。
TRIM_MEMORY_MODERATE: 系統(tǒng)正運(yùn)行于低內(nèi)存狀態(tài)并且你的進(jìn)程已經(jīng)已經(jīng)接近LRU名單的中部位置。如果系統(tǒng)開始變得更加內(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)來的,對于老的版本,你可以使用onLowMemory)回調(diào)來進(jìn)行兼容。onLowMemory相當(dāng)與TRIM_MEMORY_COMPLETE。
請注意:當(dāng)系統(tǒng)開始清除LRU緩存中的進(jìn)程時,雖然它首先按照LRU的順序來執(zhí)行操作,但是它同樣會考慮進(jìn)程的內(nèi)存使用量以及其他因素。占用越少的進(jìn)程越容易被留下來。
資源文件需要選擇合適的文件夾進(jìn)行存放
我們知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夾下的圖片在不同的設(shè)備上會經(jīng)過scale的處理。例如我們只在hdpi的目錄下放置了一張100100的圖片,那么根據(jù)換算關(guān)系,xxhdpi的手機(jī)去引用那張圖片就會被拉伸到200200。需要注意到在這種情況下,內(nèi)存占用是會顯著提高的。對于不希望被拉伸的圖片,需要放到assets或者nodpi的目錄下。
Try catch某些大內(nèi)存分配的操作
在某些情況下,我們需要事先評估那些可能發(fā)生OOM的代碼,對于這些可能發(fā)生OOM的代碼,加入catch機(jī)制,可以考慮在catch里面嘗試一次降級的內(nèi)存分配操作。例如decode bitmap的時候,catch到OOM,可以嘗試把采樣比例再增加一倍之后,再次嘗試decode。
謹(jǐn)慎使用static對象
因?yàn)閟tatic的生命周期過長,和應(yīng)用的進(jìn)程保持一致,使用不當(dāng)很可能導(dǎo)致對象泄漏,在Android中應(yīng)該謹(jǐn)慎使用static對象。
特別留意單例對象中不合理的持有
雖然單例模式簡單實(shí)用,提供了很多便利性,但是因?yàn)閱卫纳芷诤蛻?yīng)用保持一致,使用不合理很容易出現(xiàn)持有對象的泄漏。
珍惜Services資源
如果你的應(yīng)用需要在后臺使用Service,除非它被觸發(fā)并執(zhí)行一個任務(wù),否則其他時候Service都應(yīng)該是停止?fàn)顟B(tài)。另外需要注意當(dāng)這個Service完成任務(wù)之后因?yàn)橥V箂ervice失敗而引起的內(nèi)存泄漏。 當(dāng)你啟動一個Service,系統(tǒng)會傾向?yàn)榱吮A暨@個Service而一直保留Service所在的進(jìn)程。這使得進(jìn)程的運(yùn)行代價很高,因?yàn)橄到y(tǒng)沒有辦法把Service所占用的RAM空間騰出來讓給其他組件,另外Service還不能被Paged out。這減少了系統(tǒng)能夠存放到LRU緩存當(dāng)中的進(jìn)程數(shù)量,它會影響應(yīng)用之間的切換效率,甚至?xí)?dǎo)致系統(tǒng)內(nèi)存使用不穩(wěn)定,從而無法繼續(xù)保持住所有目前正在運(yùn)行的Service。 建議使用IntentService,它會在處理完交代給它的任務(wù)之后盡快結(jié)束自己。
優(yōu)化布局層次,減少內(nèi)存消耗
越扁平化的視圖布局,占用的內(nèi)存就越少,效率越高。我們需要盡量保證布局足夠扁平化,當(dāng)使用系統(tǒng)提供的View無法實(shí)現(xiàn)足夠扁平的時候考慮使用自定義View來達(dá)到目的。
謹(jǐn)慎使用多進(jìn)程
使用多進(jìn)程可以把應(yīng)用中的部分組件運(yùn)行在單獨(dú)的進(jìn)程當(dāng)中,這樣可以擴(kuò)大應(yīng)用的內(nèi)存占用范圍,但是這個技術(shù)必須謹(jǐn)慎使用,絕大多數(shù)應(yīng)用都不應(yīng)該貿(mào)然使用多進(jìn)程,一方面是因?yàn)槭褂枚噙M(jìn)程會使得代碼邏輯更加復(fù)雜,另外如果使用不當(dāng),它可能反而會導(dǎo)致顯著增加內(nèi)存。當(dāng)你的應(yīng)用需要運(yùn)行一個常駐后臺的任務(wù),而且這個任務(wù)并不輕量,可以考慮使用這個技術(shù)。
一個典型的例子是創(chuàng)建一個可以長時間后臺播放的Music Player。如果整個應(yīng)用都運(yùn)行在一個進(jìn)程中,當(dāng)后臺播放的時候,前臺的那些UI資源也沒有辦法得到釋放。類似這樣的應(yīng)用可以切分成2個進(jìn)程:一個用來操作UI,另外一個給后臺的Service。
Android內(nèi)存優(yōu)化之布局優(yōu)化
主要介紹使用抽象布局標(biāo)簽(include, viewstub, merge)、去除不必要的嵌套和View節(jié)點(diǎn)、減少不必要的infalte及其他Layout方面可調(diào)優(yōu)點(diǎn),順帶提及布局調(diào)優(yōu)相關(guān)工具(hierarchy viewer和lint)。
抽象布局標(biāo)簽
<include>標(biāo)簽
include標(biāo)簽常用于將布局中的公共部分提取出來供其他layout共用,以實(shí)現(xiàn)布局模塊化,這在布局編寫方便提供了大大的便利。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView />
<include layout="@layout/foot.xml" />
</RelativeLayout>
<include>標(biāo)簽唯一需要的屬性是layout屬性,指定需要包含的布局文件。可以定義android:id和android:layout_*屬性來覆蓋被引入布局根節(jié)點(diǎn)的對應(yīng)屬性值。注意重新定義android:id后,子布局的頂結(jié)點(diǎn)就變化了。
<ViewStub>標(biāo)簽
ViewStub標(biāo)簽同include標(biāo)簽一樣可以用來引入一個外部布局,不同的是,ViewStub引入的布局默認(rèn)不會擴(kuò)張,即既不會占用顯示也不會占用位置,從而在解析layout時節(jié)省CPU和內(nèi)存。
ViewStub常用來引入那些默認(rèn)不會顯示,只在特殊情況下顯示的布局,如進(jìn)度布局、網(wǎng)絡(luò)失敗顯示的刷新布局、信息出錯出現(xiàn)的提示布局等。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ViewStub
android:id="@+id/network_error_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/network_error" />
</RelativeLayout>
<merge>標(biāo)簽
在使用了include后可能導(dǎo)致布局嵌套過多,多余不必要的layout節(jié)點(diǎn),從而導(dǎo)致解析變慢。
merge標(biāo)簽可用于兩種典型情況:
- 布局根節(jié)點(diǎn)是FrameLayout且不需要設(shè)置background或padding等屬性,可以用merge代替,因?yàn)锳ctivity內(nèi)容試圖的parent view就是個FrameLayout,所以可以用merge消除只剩一個。
- 某布局作為子布局被其他布局include時,使用merge作為該公共布局布局的根節(jié)點(diǎn),這樣在公共布局被引入時根節(jié)點(diǎn)會自動被忽略,而將其子節(jié)點(diǎn)全部合并到主布局中。
//將xml中RelativeLayout改為merge
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_above="@+id/text"/>
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_alignParentBottom="true"
android:text="@string/app_name" />
</merge>