開篇廢話
上一篇我們了解了Android里面相關的內存泄露以及相應的處理方案秋茫,這一篇胡诗,接著上一篇的內存泄露的內容燕锥,講一下Android當中的內存溢出子刮。
內存溢出與內存泄露客叉,很多開發(fā)人員都容易產生混淆,有可能是因為這兩個概念有點關系话告,又因為名稱上也不太好區(qū)分吧。不過卵慰,我們依然要清楚沙郭,內存溢出(Out Of Memory Error) 與 內存泄露 (Memory Leak)還是有質的區(qū)別的。都我們的App多次出現(xiàn)內存泄露裳朋,可能就會導致內存溢出病线。
但是,我們的App出現(xiàn)內存溢出鲤嫡,不一定就是因為內存泄露送挑,因為本身Android系統(tǒng)分配給每一個的App的空間就是那么一點。
另外暖眼,內存泄露也不一定就會出現(xiàn)內存溢出惕耕,因為還是泄露的速度比較慢,系統(tǒng)將進程殺死了诫肠,也就不會內存溢出咯司澎,不過,發(fā)現(xiàn)內存泄露栋豫,我們還是要第一時間解決掉這個bug挤安。
技術詳情
講述邏輯如下:
1.什么是內存溢出
2.有些內存里面容易混淆的概念
3.如何解決內存溢出
1.什么是內存溢出
內存溢出,OOM(Out Of Memory),表示當前占用的內存加上我們申請的內存資源超過了Dalvik虛擬機的最大內存限制就會拋出的Out Of Memory異常丧鸯。大部分的OOM的問題蛤铜,都會與Bitmap的加載有關系
2.內存里面容易混淆的一些概念
主要有三個概念:
1.內存溢出
2.內存抖動
3.內存泄露
其中第一個內存溢出,就是剛剛講的OOM,第三個內存泄露丛肢,可以查看我的上一篇文章围肥。
關于第二個內存抖動,出現(xiàn)的情況是蜂怎,短時間內虐先,大量的對象被創(chuàng)建,然后又馬上被釋放派敷,瞬間產生的對象會嚴重占用內存區(qū)域蛹批,這個區(qū)域就是我們之前接觸的那個年輕代區(qū)域撰洗,到達這個區(qū)域的閾值時就會觸發(fā)minor gc,當出現(xiàn)頻繁的minor gc的時候,就會出現(xiàn)內存抖動腐芍,我們能夠通過我們的Android Studio的Memory Monitor能夠非常直觀的看到內存抖動
出現(xiàn)內存抖動的現(xiàn)象差导,可根據(jù)當前app處理的實際業(yè)務結合Memory Monitor中的現(xiàn)象來進行判斷,然后有針對性的進行優(yōu)化猪勇。
它們三者的重要等級分別:內存溢出 > 內存泄露 > 內存抖動
內存溢出對我們的App來說设褐,影響是非常大的,整得不好泣刹,就有可能導致程序閃退助析,無響應等現(xiàn)象,因此椅您,我們一定要優(yōu)先解決OOM的問題外冀。
3.如何解決內存溢出
如何解決OOM,這個問題范圍比較大掀泳,我這邊大概從兩個方面去講述:
1.關于Bitmap的OOM
2.除了Bitmap之外的OOM
3.1 關于Bitmap的OOM
關于Bitmap的OOM我們有幾點需要注意的雪隧。
3.1.1 ImageView等控件圖片的顯示
意思就是加載合適屬性的圖片,當我們有些場景是可以顯示縮略圖的時候员舵,就不要調用網(wǎng)絡請求加載大圖脑沿,例如在ListView中,我們在上下滑動的時候马僻,就不要去調用網(wǎng)絡請求庄拇,當監(jiān)聽到滑動結束的時候,才去加載大圖韭邓,以免上下滑動的時候產生卡頓現(xiàn)象丛忆。
3.1.2 及時釋放內存
我們知道,在Android系統(tǒng)中仍秤,本身就有自己的垃圾回收機制熄诡,系統(tǒng)會不定期進行垃圾回收的。但是诗力,這個只是針對Java那一塊的內存,但是我們需要知道Bitmap實例化的時候凰浮,是通過JNI的方式,所以還有一部分的內存是C那一塊的苇本,我們的GC沒有辦法回收袜茧,所以,我們在不用的時候瓣窄,還是需要調用recycle()方法笛厦,源碼里面,recycle()方法其實就是調用的JNI的函數(shù)俺夕,然后釋放C那一塊的內存裳凸。
3.1.3 把圖片進行壓縮
我們在實際開發(fā)過程當中贱鄙,可能因為業(yè)務需要,需要加載一張很大的圖片姨谷,大到直接可以超過系統(tǒng)分配給我們App的內存大小逗宁,這樣,就會直接導致內存溢出梦湘,那么瞎颗,這個時候,我們就應當控制圖片的大小捌议,那么就應該將bitmap進行壓縮了哼拔。
下面大概講一下對一張圖片進行壓縮的一個過程。
第一步:計算實際采樣率
/**
* 計算壓縮比例值
* @param options 解析圖片的配置信息
* @param reqWidth 所需圖片壓縮尺寸最小寬度
* @param reqHeight 所需圖片壓縮尺寸最小高度
* @return
*/
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
//保存圖片原寬高值
final int height = options. outHeight;
final int width = options. outWidth;
//初始化壓縮比例為1
int inSampleSize = 1;
//當圖片寬高值任何一個大于所需壓縮圖片寬高值時,進入循環(huán)計算系統(tǒng)
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
//壓縮比例值每次循環(huán)兩倍增加,
//直到原圖寬高值的一半除以壓縮值后都~大于所需寬高值為止
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
第二步:根據(jù)得到的采樣率對圖片進行解析
/**
* 獲取壓縮后的圖片
* @param res
* @param resId
* @param reqWidth 所需圖片壓縮尺寸最小寬度
* @param reqHeight 所需圖片壓縮尺寸最小高度
* @return
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
//首先不加載圖片,僅獲取圖片尺寸
final BitmapFactory.Options options = new BitmapFactory.Options();
//當inJustDecodeBounds設為true時,不會加載圖片僅獲取圖片尺寸信息
options.inJustDecodeBounds = true;
//此時僅會將圖片信息會保存至options對象內,decode方法不會返回bitmap對象
BitmapFactory.decodeResource(res, resId, options);
//計算壓縮比例,如inSampleSize=4時,圖片會壓縮成原圖的1/4
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
//當inJustDecodeBounds設為false時,BitmapFactory.decode...就會返回圖片對象了
options. inJustDecodeBounds = false;
//利用計算的比例值獲取壓縮后的圖片對象
return BitmapFactory.decodeResource(res, resId, options);
}
3.1.4 使用Bitmap的高級屬性inBitmap
Bitmap的inBitmap高級屬性主要是值復用內存塊瓣颅,不需要在重新給新的bitmap對象申請一塊新的內存倦逐,避免了一次內存的分配和回收,從而提供了我們程序運行的效率弄捕。
不過這個屬性還是有一些坑的,對于適配Android3.0以上 导帝。而且守谓,這個功能,google一直在優(yōu)化當中您单,在Android4.4以前斋荞,只能復用相同大小的bitmap內存,而4.4之后虐秦,則只要比之前的內存小平酿,就可以了。以下貼出inBitmap的簡單使用方法:
第一步:首先判斷當前圖片是否能夠使用inBitmap
/**
* 判斷是否能夠使用inBigmap
* @param candidate 比較標準
* @param targetOptions 判斷目標對象屬性
* @return
*/
public static boolean canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// From Android 4.4 (KitKat) onward we can re-use
// if the byte size of the new bitmap is smaller than
// the reusable bitmap candidate
// allocation byte count.
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height =
targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height
* getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
}
// On earlier versions,
// the dimensions must match exactly and the inSampleSize must be 1
return candidate.getWidth() == targetOptions.outWidth
&& candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1;
}
第二步:從緩存里面拿出bitmap,將此Bitmap賦值給inBitmap悦陋。
/**
* 將Bitmap賦值給inBitmap
* @param options 圖片的配置信息
* @param cache 圖片緩存
*/
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
//inBitmap only works with mutable bitmaps, so force the decoder to
//return mutable bitmaps.
options.inMutable = true;
if (cache != null) {
// Try to find a bitmap to use for inBitmap.
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null) {
// If a suitable bitmap has been found,
// set it as the value of inBitmap.
options.inBitmap = inBitmap;
}
}
}
第三步:調用剛剛圖片壓縮時候的decode方法蜈彼,把options參數(shù)傳入
3.1.5 捕獲異常
很多時候,當內存確實很吃緊的時候俺驶,難免還是會出現(xiàn)OOM幸逆,所以,根據(jù)經(jīng)驗之談暮现,我們在開發(fā)過程中还绘,實例化Bitmap的時候,最好還是添加try catch栖袋,進行異常捕獲拍顷。
需要注意,平常的Exception異常是捕獲不到OOM Erro的塘幅,因為OOM是一個錯誤昔案,我們編碼的時候需要捕獲錯誤尿贫,具體給出以下示例代碼:
public static Bitmap createBitmap(int width, int height, Bitmap.Config config) {
Bitmap bitmap = null;
try {
bitmap = Bitmap.createBitmap(width, height, config);
} catch (OutOfMemoryError e) {
while(bitmap == null) {
System.gc();
System.runFinalization();
bitmap = createBitmap(width, height, config);
}
}
}
3.2 除了Bitmap之外的OOM
3.2.1 listview
這個listview確實提到了好多次,畢竟我們實際開發(fā)當中爱沟,用它來呈現(xiàn)一些數(shù)據(jù)確實的頻率也蠻高帅霜,還是需要講述一下。
使用listview的時候呼伸,一定要記得復用convertView
同時身冀,在listview當中,如果需要顯示大圖的控件括享,記得使用LRU(最近最少使用搂根,三級緩存)機制進行緩存圖片
3.2.2 onDraw方法當中,盡量避免對象的創(chuàng)建
如果在onDraw方法中創(chuàng)建對象铃辖,會觸發(fā)頻繁的GC,也就是之前提到的內存抖動剩愧,當內存抖動積累到一定的程度,也會出現(xiàn)內存溢出娇斩。
3.2.3 使用多進程仁卷,一定要小心小心再小心
我們有的時候需要將一些服務,或者主件放到另外一個進程去運行犬第,例如一些定位锦积,推送等,這樣確實可以分擔主進程的內存壓力歉嗓。
但是丰介,多進程中的一些通信真心沒有那么簡單。很多機制可能失效鉴分,從而影響業(yè)務的基本功能哮幢。可能會出現(xiàn)一些莫名其妙的crash.
所以志珍,如果我們的App實際業(yè)務沒有達到一定程度橙垢,真心不要使用多進程。
干貨總結
此篇文章根據(jù)OOM是什么伦糯,了解一些容易混淆的概念钢悲,然后熟悉一些OOM的解決方案這個邏輯,再結合實際開發(fā)可能遇到的問題舔株,講述了內存溢出的相關知識莺琳。其實,大篇幅都是在講述Bitmap的處理方案载慈,因為惭等,我們這個Bitmap確實在實際開發(fā)當中引發(fā)OOM的概率還是相當大的。
希望通過以上的講述办铡,我們能夠對于OOM有一個清晰的了解辞做,從而根據(jù)我們實際開發(fā)當中自己的業(yè)務琳要,進行OOM的優(yōu)化。
其實有的時候秤茅,我們在解決OOM的時候需要有一個權衡稚补,因為如果考慮到了OOM的情況而頻繁觸發(fā)GC,可能會導致UI卡頓的現(xiàn)象,跟嚴重的可能出現(xiàn)ANR的問題框喳,需要我們在實際開發(fā)過程中具體場景具體分析课幕。
好了,內存的泄露的知識就先更新到這了五垮,如果覺得本篇文章對大家有益乍惊,請給予一個贊和喜歡,這樣我才更有動力一直更新下去放仗,如果想和我一起探討的润绎,可以關注一波。