我們很常使用Bitmap來存放圖片,但是常常因為檔案太大,
一不小心就跳出Out of Memory(OOM)的訊息,
為什麼會產生OOM的問題呢?
原因就出在Android的限制, Android是跑在手機上的作業系統,
因此我們手機上的記憶體就不可能像PC上面的記憶體一樣,
想擴增就擴增, 因此每一點一滴的記憶體我們都必須斤斤計較,
而Android的限制就是當你超過16MB的檔案, 它就會跳出OOM的警告視窗,
警告你不能使用這麼大的檔案。
從這個角度來想, 我們就有幾個思考的方向,
1. 從圖檔著手。
2. 從程式碼著手。
第一點沒什麼好說的, 如果你想要下載大圖到手機上或者平板上,
通常是沒什麼必要的, 畢竟人的眼睛對於解析度高跟解析度普通的圖片,
看不出太大的差別, 所以建議最好不要找那麼大的解析度。
所以我們就單純討論第二點,
引述這裡的方法
http://www.ptt.cc/bbs/AndroidDev/M.1308308761.A.77E.html
解決方式大概有以下幾種:
1. 使用 BitmapFactory.decodeStream() 產生Bitmap
2. 設定 BitmapFactory.Options inSampleSize,
(inSampleSize值越大解析度越小,佔用memory也越小)
3. 在 onPause/onStop/onDestory 時,將沒用到的Bitmap release(用recyle())
根據第一點
我們可以這樣寫,
public Bitmap getLocalBitmap(Context con, int resourceId){
InputStream inputStream = con.getResources().openRawResource(resourceId);
return BitmapFactory.decodeStream(inputStream, null, getBitmapOptions());
}
當你傳入一個Resource id的時候, 利用context的方法把它轉成串流, 就可以使用BitmapFactory.decodeStream這個方法, 為什麼要使用這個方法來改善呢?
根據這篇文章
http://shukaiyang.myweb.hinet.net/notes/mobile/android_oom.htm
裡面所敘述的
decodeStream最大的秘密在於其直接調用JNI>>nativeDecodeAsset()來完成decode,無需再使用java層的createBitmap,從而節省了java層的空間。
因此我們用這樣的方法來讀取專案內的圖檔應該是最恰當的。
接著第二點
如果一開始就把圖片設定為小圖片, 那麼想當然圖片容量就會變小,
我們可以這樣寫
public BitmapFactory.Options getBitmapOptions(int scale){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inInputShareable = true;
options.inSampleSize = scale;
return options;
}
inPurgeable 設定為 true,這樣可以讓java系統記憶體不足時先行回收部分的記憶體
而根據官網的敘述
http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html
public boolean inInputShareable : This field works in conjuction with inPurgeable.
因此我們就將這兩個參數都設定為true
最後是我們inSimpleSize的大小由程式撰寫者來設定,
我通常看圖片大小大概設2~4就差不多了。
那麼第三點的討論
也就是說在程式中的各種狀態, 隨時就來個
bitmap.recycle();
System.gc();
通知一下系統, 該回收了喔!
其實我還找到一些方法可以稍微改善一下記憶體的使用程度,
例如
在BitmapFactory.Options裏竟然有一個inNativeAlloc的public變數,可以直接不把使用的記憶體算到VM裏,有趣的是這個變數是個隱藏版的變數,所以在正常的SDK文件中看不到,用eclipse時也不會提示你,也不能直接用,因此我用了一些小技巧將這個變數設成true,如此一來bitmap out of memory的問題發生的機率又更低了
寫法大概像這樣
BitmapFactory.Options.class.getField("inNativeAlloc").setBoolean(options,true);
還有
Android堆記憶體也可自己定義大小
對於一些Android專案,影響性能瓶頸的主要是Android自己記憶體管理機制問題,目前手機廠商對RAM都比較吝嗇,對於軟體的流暢性來說RAM對性能的影響十分敏感,除了 優化Dalvik虛擬機的堆記憶體分配外,我們還可以強制定義自己軟體的對記憶體大小,我們使用Dalvik提供的 dalvik.system.VMRuntime類來設置最小堆記憶體為例:
寫法像這樣
private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
還有雖然前面的連結有說過, 非必要的時候, 盡量不要使用BitmapFactory.createBitmap來建立
一張bitmap, 但是其實createBitmap也是可以優化的。
http://topic.csdn.net/u/20100208/15/acfacf0b-654e-4f8e-82c3-a240277fa2b6.html
Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
這四種分別代表著不同的意義
Bitmap.Config ALPHA_8
Bitmap.Config ARGB_4444
Bitmap.Config ARGB_8888
Bitmap.Config RGB_565
A R G B
透明度 红色 绿色 蓝色
Bitmap.Config ARGB_4444 每個pixel 佔四位
Bitmap.Config ARGB_8888 32 每個pixel 佔八位
Bitmap.Config RGB_565 16 R佔5位 G佔6位 B占5位 没有透明度(A)
其實很簡單 就跟double float一樣 你解析度越高圖檔就越大 無可厚非
一般情況下我們都是用ARGB_8888 但是相對它也佔相當大的記憶體
因為一個pixel 32bit 8個bit=1word 如果是800*400的圖片 則會變成1MB多