Android應用中常常有加載圖片資源的操作,隨着Android手機平板的分辨率越來越高,圖片資源越來越大,在加載高清圖片的時候,由於瞬間產生大量的內存消耗,有時java GC來不及進行垃圾回收,就很容易發生OOM現象,怎么優化加載圖片呢?本文簡單介紹一下圖片加載的優化。
方法一:BitmapFactory.Options的兩個參數inPurgeable、inNativeAlloc
先來看一段神奇的代碼
1 public Bitmap decodeFile(String filePath) { 2 Bitmap bitmap = null; 3 BitmapFactory.Options options = new BitmapFactory.Options(); 4 options.inPurgeable = true; 5 try { 6 BitmapFactory.Options.class.getField("inNativeAlloc").setBoolean( 7 options, true); 8 } catch (IllegalArgumentException e) { 9 e.printStackTrace(); 10 } catch (SecurityException e) { 11 e.printStackTrace(); 12 } catch (IllegalAccessException e) { 13 e.printStackTrace(); 14 } catch (NoSuchFieldException e) { 15 e.printStackTrace(); 16 } 17 if (mFilePath != null) { 18 bitmap = BitmapFactory.decodeFile(mFilePath, options); 19 } 20 return bitmap; 21 }
關於inPurgeable
處理過位圖加載的人可能對BitmapFactory.Options的inPurgeable參數比較熟悉,當inPurgeable==true時,可以讓java系統內存不足時先行回收部分的內存,這個方法其實已經解決大部分的問題了。
關於inNativeAlloc
那么try-catch里面做了什么呢?在看了source code 之后,我發現在BitmapFactory.Options里竟然有一個inNativeAlloc的public變量,可以直接不把使用的內存算到VM里,相應的,inPurgeable生出來的內存還是算在java 的VM里。需要注意的這個變量是個隱藏的變量,不能直接用,需要用反射將這個變量設成true。如此一來bitmap OOM的問題發生的機率又更低了,有需要的人可以參考一下。
方法二:使用BitmapFactory.decodeStream
同樣先show code
1 public Bitmap ReadBitMap(Context context, int resId){ 2 BitmapFactory.Options opt = new BitmapFactory.Options(); 3 opt.inPreferredConfig = Bitmap.Config.RGB_565; 4 opt.inPurgeable = true; 5 opt.inInputShareable = true; 6 InputStream is = context.getResources().openRawResource(resId); 7 return BitmapFactory.decodeStream(is,null,opt); 8 }
關於BitmapFactory.decodeStream
當我們給view設置圖片資源時,使用像 setBackgroundResource,setImageResource,或者 BitmapFactory.decodeResource 這樣的方法來設置一張高清圖片的時候,這些函數在完成decode后,最終都是通過java層的createBitmap來完成的,需要消耗更多內存。而改用先通過BitmapFactory.decodeStream方法,創建出一個bitmap,再將其設為ImageView的source。decodeStream的優勢在於其直接調用JNI>>nativeDecodeAsset()來完成decode,無需再使用java層的createBitmap,從而節省了java層的空間。如果在讀取時加上圖片的Config參數,可以跟有效減少加載的內存,從而跟有效阻止拋out of Memory異常。
另外,需要特別注意:decodeStream是直接讀取圖片資料的字節碼了, 不會根據機器的各種分辨率來自動適應,使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相應的圖片資源,否則在不同分辨率機器上都是同樣大小(像素點數量),顯示出來的大小就不對了。
方法三:使用內存緩存
內存緩存很多人都用,但是內存緩存設置需要得當,太小了會導致緩存不夠用,太大了會導致其他應用可用內存減小,也容易造成內存溢出。
1 private LruCache<String, Bitmap> mMemoryCache; 2
3 @Override 4 protected void onCreate(Bundle savedInstanceState) { 5 ... 6 // 獲取到虛擬機的可用最大內存
7 final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 8
9 // 只使用1/8的空閑內存作為緩存空間.
10 final int cacheSize = maxMemory / 8; 11
12 mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 13 @Override 14 protected int sizeOf(String key, Bitmap bitmap) { 15 return bitmap.getByteCount() / 1024; 16 } 17 }; 18 ... 19 } 20
21 public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 22 if (getBitmapFromMemCache(key) == null) { 23 mMemoryCache.put(key, bitmap); 24 } 25 } 26
27 public Bitmap getBitmapFromMemCache(String key) { 28 return mMemoryCache.get(key); 29 }
設置好內存緩存區,我們就可以在需要的時候從內存緩存區直接拿到圖片了:
1 public void loadBitmap(int resId, ImageView imageView) { 2 final String imageKey = String.valueOf(resId); 3
4 final Bitmap bitmap = getBitmapFromMemCache(imageKey); 5 if (bitmap != null) { 6 mImageView.setImageBitmap(bitmap); 7 } else { 8 mImageView.setImageResource(R.drawable.default_drawable); 9 BitmapWorkerTask task = new BitmapWorkerTask(mImageView); 10 task.execute(resId); 11 } 12 }
當然了,在bitmap是新的圖片時,需要把圖片放到緩存區中去:
1 class BitmapWorkerTask extends AsyncTask { 2 ... 3 @Override 4 protected Bitmap doInBackground(Integer... params) { 5 final Bitmap bitmap = decodeSampledBitmapFromResource( 6 getResources(), params[0], 100, 100)); 7 addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); 8 return bitmap; 9 } 10 ... 11 }
附:別的一些優化思想
總結了下別的一些優化思想,有的是對圖片進行壓縮,比如說圖片寬高像素大於空間時,就先把圖片進行壓縮,再decodeResource,不過一般應用開發時較少遇到這樣的情況。還有的是使用SDcard卡進行緩存,這樣即使應用被意外退出了,也不會丟失緩存,但是這中間需要應用到IO操作,IO操作向來是比較慢的,也不是很推薦。