1. 程式人生 > >Android實踐:高效載入Bitmap

Android實踐:高效載入Bitmap

轉自 http://blog.csdn.net/p106786860/article/details/53260463

一、BitmapFactory.Options簡介
在Android開發中,載入圖片過多、過大很容易引起OutOfMemoryError異常,即我們常見的記憶體溢位。因為Android對單個應用施加記憶體限制,預設分配的記憶體只有幾M(具體視不同系統而定)。而載入的圖片如果是JPG之類的壓縮格式(JPG支援最高級別的壓縮,不過該壓縮是有損的),在記憶體中展開會佔用大量的記憶體空間,也就容易形成記憶體溢位;

那麼高效的載入Bitmap是很重要的事情。Bitmap在Android中指的是一張圖片,可以是png格式也可以是jpg等常見的格式。BitmapFactory提供瞭如下四類方法,可分別用於從檔案系統、資源、輸入流以及位元組陣列中加載出一個Bitmap物件
  1.decodeFile;

  2.decodeResource;
  3.decodeStream;
  4.decodeByteArray;

如果高效的載入類圖呢,其實核心就是BitmapFactory.Options來載入所需尺寸的圖片。因為很多時間ImageView並沒有圖片原始尺寸那麼大,把整個圖片載入進來顯然是沒有必要的。我們可以使用BitmapFactory.Options從如下幾種方式對圖片進行取樣壓縮,降低記憶體的佔有從而減少OOM的可能性:
  1. 降低圖片載入到記憶體的解析度(BitmapFactory.Options.inJustDecodeBounds/outWidth/outHeight/inSampleSize屬性);
  2. 採用更節更節省記憶體的編碼,如ARGB_4444(BitmapFactory.Options.inPreferredConfig屬性);
  3. 採用快取;
這裡我們就從方法1和方法2進行處理,該方式需要了解BitmapFactory.Options,先介紹如下:
引數 說明 備註
inJustDecodeBounds 為true時,解碼不會返回bitmap,只會返回bitmap的尺寸。 用於獲取圖片的尺寸,但有不想將其載入到記憶體中。
inSampleSize 當<1時,當做1處理;>1時會按照比例縮小bitmap的寬高,降低解析度。 如with=100,height=100,inSampleSize=2,則返回width=50,height=50,畫素50*50=250降為1/4;
inPreferredConfig 色彩模式,預設ARGB_8888,一個畫素4bytes。如果對透明不做要求,採用RGB_565,一個畫素2bytes;
ALPHA_8:每個畫素1byte; ARGB_444:每個畫素2byte; ARGB_8888:每個畫素4byte; RGB_565:每個畫素2byte; 如果一個圖片解析度1024*768,採用ARGB_8888,佔用空間為1024*768*4=3M,而採用ARGB_444記憶體就能減半1.5M;
inPremultiplied 和透明通道有關,預設true,返回的bitmap顏色通道上預先附加透明通道; 透明通道是計算機圖形學術語,指的是“非彩色”通道,8位灰度通道,使用256級灰度來記錄影象中的透明資訊,定義透明、不透明和半透明。如32位儲存的圖片,8紅+8綠+8藍+8透明;
inDither 抖動解碼,預設false,標識不採用抖動解碼; Bitmap解碼是根據它所記錄的節點,按照一定的演算法來補充兩個節點之間的資料,可理解為補充畫素點的顏色。一張顏色豐富的圖用一個位數比較低的顏色模式解碼的話,會感覺顏色不夠用,顏色漸變區域有明顯斷裂帶。因為一些豐富的顏色在位數較低的顏色模式下並沒有,只能用相近的顏色補充,可能一大片沒有,那麼這大片都用一個顏色填充,就形成了斷裂色帶;如果採用抖動解碼,就會在這些顏色上採用隨機噪聲色來填充,這樣顯示效果更好,色帶不那麼明顯。如果不想有這些色帶,就需要採用抖動解碼;
inDensity 表示這個bitmap的畫素密度; 對應DisplayMetrics.densityDpi,不是density
inTargetDensity 表示要被畫出來時的目標畫素密度;
inScreenDensity 標識實際裝置的畫素密度; inDensity,inTargetDensity,inScreenDensity這三個值的目的就是為了確定這個Bitmap的寬高和density。詳細演算法可以檢視setDensityFromOptions()方法原始碼實現;
inScaled 設定這個bitmap是否可以被縮放,預設true;
inPurgeable/inInputShareable 一般一起使用,設定為true時,表示空間不夠可以被釋放,後者表示是否可以共享引用。Android5.0後被棄用;
outWidth/outHeight 表示bitmap的寬和高,一般和inJustDecodeBounds一起使用獲取Bitmap的寬高,但不載入到記憶體中;

二、BitmapFactory.Options實踐
1.為了跟大家更好的體會和展示優化過程,首先我們先使用一個簡單Demo,使用Gallery來展示幾張圖片來模擬OOM,程式碼如下:
NextActivity.java:
  1. publicclass NextActivity extends AppCompatActivity {  
  2.     privateint[] images = newint[]{R.drawable.p1, R.drawable.p2, R.drawable.p3};  
  3.     @Override
  4.     protectedvoid onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_next);  
  7.         Gallery gallery = (Gallery) findViewById(R.id.gallery);  
  8.         gallery.setAdapter(new GalleryAdapter());  
  9.     }  
  10.     class GalleryAdapter extends BaseAdapter {  
  11.         @Override
  12.         publicint getCount() {  
  13.             return images.length;  
  14.         }  
  15.         @Override
  16.         public Object getItem(int position) {  
  17.             return images[position];  
  18.         }  
  19.         @Override
  20.         publiclong getItemId(int position) {  
  21.             return images[position];  
  22.         }  
  23.         @Override
  24.         public View getView(int position, View convertView, ViewGroup parent) {  
  25.             ImageView imageView = new ImageView(NextActivity.this);  
  26.             Bitmap bitmap = BitmapFactory.decodeResource(getResources(), images[position]);  
  27.             imageView.setImageBitmap(bitmap);  
  28.             return imageView;le = true;  
  29.         }  
  30.     }  
  31. }  
2.執行後即OOM異常崩潰,錯誤日誌輸出如下:

再看看Monitor中,在兩張圖片載入圖片過程中,記憶體兩次迅速上升,達到200M後OOM崩潰;

3.接下來下面我們就使用BitmapFactory.Options來優化該OOM問題:

NextActivity.java
  1. publicclass NextActivity extends AppCompatActivity {  
  2.     ... ...  
  3.     class GalleryAdapter extends BaseAdapter {  
  4.         ... ...  
  5.         @Override
  6.         public View getView(int position, View convertView, ViewGroup parent) {  
  7.             ImageView imageView = new ImageView(NextActivity.this);  
  8.             BitmapFactory.Options options = new BitmapFactory.Options();  
  9.             //inJustDecodeBounds為true,不返回bitmap,只返回這個bitmap的尺寸
  10.             options.inJustDecodeBounds = true;  
  11.             BitmapFactory.decodeResource(getResources(), images[position], options);  
  12.             //利用返回的原圖片的寬高,我們就可以計算出縮放比inSampleSize,獲取指定寬度為300畫素,等長寬比的縮圖,減少圖片的畫素
  13.             int toWidth = 300;  
  14.             int toHeight = options.outHeight * toWidth / options.outWidth;  
  15.             options.inSampleSize = options.outWidth / toWidth;  
  16.             options.outWidth = toWidth;  
  17.             options.outHeight = toHeight;  
  18.             //使用RGB_565減少圖片大小
  19.             options.inPreferredConfig = Bitmap.Config.RGB_565;  
  20.             //釋放記憶體,共享引用(21版本後失效)
  21.             options.inPurgeable = true;  
  22.             options.inInputShareable = true;  
  23.             //inJustDecodeBounds為false,返回bitmap
  24.             options.inJustDecodeBounds = false;  
  25.             Bitmap bitmap = BitmapFactory.decodeResource(getResources(), images[position], options);  
  26.             imageView.setImageBitmap(bitmap);  
  27.             return imageView;  
  28.         }  
  29.     }  
  30. }  
4.程式正常執行,Monitor監控記憶體減少至21M左右!!!


5.在實際的應用過過程中,inSampleSize計算並不會那麼“理想 ”。比如ImagView的大小是100*100畫素,而圖片的原始大小是200*300呢?inSampleSize為2,則縮放後的圖片大小為100*150畫素,仍然是適合的;但是為3那麼縮小後的圖片大小就會小於ImageView的期望大小,這樣圖片就會拉伸從而導致模糊。下面我們就提供一種計算inSampleSize的計算方式,供大家參考:
  1. publicstaticint caculateInSampleSize(BitmapFactory.Options,int reqWidth,int reqHeight){  
  2.      finalint height = options.outHeight;  
  3.      finalint width = options.outWidth;  
  4.      int inSampleSize = 1;  
  5.      if(height > reqHeight || width > reqWidth){  
  6.           finalint halfHeight = height / 2;  
  7.           finalint halfWidth = width / 2;   
  8.           while(((halfHeight / inSampleSize) >= reqHeight) && ((halfWidth / inSampleSize) >=reqWidth)){  
  9.                inSampleSize *= 2;  
  10.           }  
  11.      }  
  12.      return inSampleSize;  
  13. }  

6.程式碼庫
QProject:https://github.com/Pengchengxiang/QProject 分支:feature/bitmapoption