Bitmap引起的OOM問(wèn)題
本節(jié)引言:
上節(jié),我們已經(jīng)學(xué)習(xí)了Bitmap的基本用法,而本節(jié)我們要來(lái)探討的Bitmap的OOM問(wèn)題, 大家在實(shí)際開(kāi)發(fā)中可能遇到過(guò),或者沒(méi)遇到過(guò)因?yàn)锽itmap引起的OOM問(wèn)題,本節(jié)我們 就來(lái)圍繞這個(gè)話題來(lái)進(jìn)行學(xué)習(xí)~了解什么是OOM,為什么會(huì)引起OOM,改善因Bitmap引起的 OOM問(wèn)題~
1.什么是OOM?為什么會(huì)引起OOM?
答:Out Of Memory(內(nèi)存溢出),我們都知道Android系統(tǒng)會(huì)為每個(gè)APP分配一個(gè)獨(dú)立的工作空間, 或者說(shuō)分配一個(gè)單獨(dú)的Dalvik虛擬機(jī),這樣每個(gè)APP都可以獨(dú)立運(yùn)行而不相互影響!而Android對(duì)于每個(gè) Dalvik虛擬機(jī)都會(huì)有一個(gè)最大內(nèi)存限制,如果當(dāng)前占用的內(nèi)存加上我們申請(qǐng)的內(nèi)存資源超過(guò)了這個(gè)限制 ,系統(tǒng)就會(huì)拋出OOM錯(cuò)誤!另外,這里別和RAM混淆了,即時(shí)當(dāng)前RAM中剩余的內(nèi)存有1G多,但是OOM還是會(huì)發(fā)生!別把RAM(物理內(nèi)存)和OOM扯到一起!另外RAM不足的話,就是殺應(yīng)用了,而不是僅僅是OOM了! 而這個(gè)Dalvik中的最大內(nèi)存標(biāo)準(zhǔn),不同的機(jī)型是不一樣的,可以調(diào)用:
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); Log.e("HEHE","最大內(nèi)存:" + activityManager.getMemoryClass());
獲得正常的最大內(nèi)存標(biāo)準(zhǔn),又或者直接在命令行鍵入:
adb shell getprop | grep dalvik.vm.heapgrowthlimit
你也可以打開(kāi)系統(tǒng)源碼/system/build.prop文件,看下文件中這一部分的信息得出:
dalvik.vm.heapstartsize=8m dalvik.vm.heapgrowthlimit=192m dalvik.vm.heapsize=512m dalvik.vm.heaptargetutilization=0.75 dalvik.vm.heapminfree=2m dalvik.vm.heapmaxfree=8m
我們關(guān)注的地方有三個(gè):heapstartsize堆內(nèi)存的初始大小,heapgrowthlimit標(biāo)準(zhǔn)的應(yīng)用的最大堆 內(nèi)存大小,heapsize則是設(shè)置了使用android:largeHeap的應(yīng)用的最大堆內(nèi)存大?。?/p>
我這里試了下手頭幾個(gè)機(jī)型的正常最大內(nèi)存分配標(biāo)準(zhǔn):
你也可以試試自己手頭的機(jī)子~
好啦,不扯了,關(guān)于OOM問(wèn)題的產(chǎn)生,就扯到這里,再扯就到內(nèi)存管理那一塊了,可是個(gè)大塊頭, 現(xiàn)在還啃不動(dòng)...下面我們來(lái)看下避免Bitmap OOM的一些技巧吧!
2.避免Bitmap引起的OOM技巧小結(jié)
1)采用低內(nèi)存占用量的編碼方式
上一節(jié)說(shuō)了BitmapFactory.Options這個(gè)類(lèi),我們可以設(shè)置下其中的inPreferredConfig屬性,
默認(rèn)是Bitmap.Config.ARGB_8888,我們可以修改成Bitmap.Config.ARGB_4444
Bitmap.Config ARGB_4444:每個(gè)像素占四位,即A=4,R=4,G=4,B=4,那么一個(gè)像素點(diǎn)占4+4+4+4=16位
Bitmap.Config ARGB_8888:每個(gè)像素占八位,即A=8,R=8,G=8,B=8,那么一個(gè)像素點(diǎn)占8+8+8+8=32位
默認(rèn)使用ARGB_8888,即一個(gè)像素占4個(gè)字節(jié)!
2)圖片壓縮
同樣是BitmapFactory.Options,我們通過(guò)inSampleSize設(shè)置縮放倍數(shù),比如寫(xiě)2,即長(zhǎng)寬變?yōu)樵瓉?lái)的1/2,圖片就是原來(lái)的1/4,如果不進(jìn)行縮放的話設(shè)置為1即可!但是不能一味的壓縮,畢竟這個(gè)值太小 的話,圖片會(huì)很模糊,而且要避免圖片的拉伸變形,所以需要我們?cè)诔绦蛑袆?dòng)態(tài)的計(jì)算,這個(gè) inSampleSize的合適值,而Options中又有這樣一個(gè)方法:inJustDecodeBounds,將該參數(shù)設(shè)置為 true后,decodeFiel并不會(huì)分配內(nèi)存空間,但是可以計(jì)算出原始圖片的長(zhǎng)寬,調(diào)用 options.outWidth/outHeight獲取出圖片的寬高,然后通過(guò)一定的算法,即可得到適合的 inSampleSize,這里感謝街神提供的代碼——摘自鴻洋blog!
public static int caculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { int width = options.outWidth; int height = options.outHeight; int inSampleSize = 1; if (width > reqWidth || height > reqHeight) { int widthRadio = Math.round(width * 1.0f / reqWidth); int heightRadio = Math.round(height * 1.0f / reqHeight); inSampleSize = Math.max(widthRadio, heightRadio); } return inSampleSize; }
然后使用下上述的方法即可:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; // 設(shè)置了此屬性一定要記得將值設(shè)置為false Bitmap bitmap = null; bitmap = BitmapFactory.decodeFile(url, options); options.inSampleSize = computeSampleSize(options,128,128); options.inPreferredConfig = Bitmap.Config.ARGB_4444; /* 下面兩個(gè)字段需要組合使用 */ options.inPurgeable = true; options.inInputShareable = true; options.inJustDecodeBounds = false; try { bitmap = BitmapFactory.decodeFile(url, options); } catch (OutOfMemoryError e) { Log.e(TAG, "OutOfMemoryError"); }
3.及時(shí)回收?qǐng)D像
如果引用了大量的Bitmap對(duì)象,而應(yīng)用又不需要同時(shí)顯示所有圖片??梢詫簳r(shí)不用到的Bitmap對(duì)象 及時(shí)回收掉。對(duì)于一些明確知道圖片使用情況的場(chǎng)景可以主動(dòng)recycle回收,比如引導(dǎo)頁(yè)的圖片,使用 完就recycle,幀動(dòng)畫(huà),加載一張,畫(huà)一張,釋放一張!使用時(shí)加載,不顯示時(shí)直接置null或recycle! 比如:imageView.setImageResource(0); 不過(guò)某些情況下會(huì)出現(xiàn)特定圖片反復(fù)加載,釋放,再加載等,低效率的事情...
4.其他方法
下面這些方法,我并沒(méi)有用過(guò),大家可以自行查閱相關(guān)資料:
1.簡(jiǎn)單通過(guò)SoftReference引用方式管理圖片資源
建個(gè)SoftReference的hashmap 使用圖片時(shí)先查詢這個(gè)hashmap是否有softreference, softreference里的圖片是否為空, 如果為空就加載圖片到softreference并加入hashmap。 無(wú)需再代碼里顯式的處理圖片的回收與釋放,gc會(huì)自動(dòng)處理資源的釋放。 這種方式處理起來(lái)簡(jiǎn)單實(shí)用,能一定程度上避免前一種方法反復(fù)加載釋放的低效率。但還不夠優(yōu)化。
示例代碼:
private Map<String, SoftReference> imageMap = new HashMap<String, SoftReference>(); public Bitmap loadBitmap(final String imageUrl,final ImageCallBack imageCallBack) { SoftReference reference = imageMap.get(imageUrl); if(reference != null) { if(reference.get() != null) { return reference.get(); } } final Handler handler = new Handler() { public void handleMessage(final android.os.Message msg) { //加入到緩存中 Bitmap bitmap = (Bitmap)msg.obj; imageMap.put(imageUrl, new SoftReference(bitmap)); if(imageCallBack != null) { imageCallBack.getBitmap(bitmap); } } }; new Thread(){ public void run() { Message message = handler.obtainMessage(); message.obj = downloadBitmap(imageUrl); handler.sendMessage(message); } }.start(); return null ; } // 從網(wǎng)上下載圖片 private Bitmap downloadBitmap (String imageUrl) { Bitmap bitmap = null; try { bitmap = BitmapFactory.decodeStream(new URL(imageUrl).openStream()); return bitmap ; } catch (Exception e) { e.printStackTrace(); return null; } } public interface ImageCallBack{ void getBitmap(Bitmap bitmap); }
2.LruCache + sd的緩存方式
Android 3.1版本起,官方還提供了LruCache來(lái)進(jìn)行cache處理,當(dāng)存儲(chǔ)Image的大小大于LruCache 設(shè)定的值,那么近期使用次數(shù)最少的圖片就會(huì)被回收掉,系統(tǒng)會(huì)自動(dòng)釋放內(nèi)存!
使用示例:
步驟:
1)要先設(shè)置緩存圖片的內(nèi)存大小,我這里設(shè)置為手機(jī)內(nèi)存的1/8, 手機(jī)內(nèi)存的獲取方式:int MAXMEMONRY = (int) (Runtime.getRuntime() .maxMemory() / 1024);
2)LruCache里面的鍵值對(duì)分別是URL和對(duì)應(yīng)的圖片
3)重寫(xiě)了一個(gè)叫做sizeOf的方法,返回的是圖片數(shù)量。
private LruCache mMemoryCache; private LruCacheUtils() { if (mMemoryCache == null) mMemoryCache = new LruCache( MAXMEMONRY / 8) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 重寫(xiě)此方法來(lái)衡量每張圖片的大小,默認(rèn)返回圖片數(shù)量。 return bitmap.getRowBytes() * bitmap.getHeight() / 1024; } @Override protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { Log.v("tag", "hard cache is full , push to soft cache"); } }; }
4)下面的方法分別是清空緩存、添加圖片到緩存、從緩存中取得圖片、從緩存中移除。
移除和清除緩存是必須要做的事,因?yàn)閳D片緩存處理不當(dāng)就會(huì)報(bào)內(nèi)存溢出,所以一定要引起注意。
public void clearCache() { if (mMemoryCache != null) { if (mMemoryCache.size() > 0) { Log.d("CacheUtils", "mMemoryCache.size() " + mMemoryCache.size()); mMemoryCache.evictAll(); Log.d("CacheUtils", "mMemoryCache.size()" + mMemoryCache.size()); } mMemoryCache = null; } } public synchronized void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (mMemoryCache.get(key) == null) { if (key != null && bitmap != null) mMemoryCache.put(key, bitmap); } else Log.w(TAG, "the res is aready exits"); } public synchronized Bitmap getBitmapFromMemCache(String key) { Bitmap bm = mMemoryCache.get(key); if (key != null) { return bm; } return null; } /** * 移除緩存 * * @param key */ public synchronized void removeImageCache(String key) { if (key != null) { if (mMemoryCache != null) { Bitmap bm = mMemoryCache.remove(key); if (bm != null) bm.recycle(); } } }
上述內(nèi)容摘自——圖片緩存之內(nèi)存緩存技術(shù)LruCache,軟引用
本節(jié)小結(jié):
本節(jié)給大家講解了OOM問(wèn)題的發(fā)生緣由,也總結(jié)了一下網(wǎng)上給出的一些避免因Bitmap而引起OOM 的一些方案,因?yàn)楣咀龅腁PP都是地圖類(lèi)的,很少涉及到圖片,所以筆者并沒(méi)有遇到過(guò)OOM的問(wèn)題, 所以對(duì)此并不怎么熟悉~后續(xù)在進(jìn)階課程的內(nèi)存管理,我們?cè)俾m結(jié)這個(gè)OOM的問(wèn)題,好的, 本節(jié)就到這里,謝謝~
參考文獻(xiàn):Android應(yīng)用中OOM問(wèn)題剖析和解決方案