Android實踐:高效載入Bitmap
阿新 • • 發佈:2019-02-05
轉自 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;
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:
- publicclass NextActivity extends AppCompatActivity {
- privateint[] images = newint[]{R.drawable.p1, R.drawable.p2, R.drawable.p3};
- @Override
- protectedvoid onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_next);
- Gallery gallery = (Gallery) findViewById(R.id.gallery);
- gallery.setAdapter(new GalleryAdapter());
- }
- class GalleryAdapter extends BaseAdapter {
- @Override
- publicint getCount() {
- return images.length;
- }
- @Override
- public Object getItem(int position) {
- return images[position];
- }
- @Override
- publiclong getItemId(int position) {
- return images[position];
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ImageView imageView = new ImageView(NextActivity.this);
- Bitmap bitmap = BitmapFactory.decodeResource(getResources(), images[position]);
- imageView.setImageBitmap(bitmap);
- return imageView;le = true;
- }
- }
- }
再看看Monitor中,在兩張圖片載入圖片過程中,記憶體兩次迅速上升,達到200M後OOM崩潰;
3.接下來下面我們就使用BitmapFactory.Options來優化該OOM問題:
NextActivity.java
- publicclass NextActivity extends AppCompatActivity {
- ... ...
- class GalleryAdapter extends BaseAdapter {
- ... ...
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ImageView imageView = new ImageView(NextActivity.this);
- BitmapFactory.Options options = new BitmapFactory.Options();
- //inJustDecodeBounds為true,不返回bitmap,只返回這個bitmap的尺寸
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeResource(getResources(), images[position], options);
- //利用返回的原圖片的寬高,我們就可以計算出縮放比inSampleSize,獲取指定寬度為300畫素,等長寬比的縮圖,減少圖片的畫素
- int toWidth = 300;
- int toHeight = options.outHeight * toWidth / options.outWidth;
- options.inSampleSize = options.outWidth / toWidth;
- options.outWidth = toWidth;
- options.outHeight = toHeight;
- //使用RGB_565減少圖片大小
- options.inPreferredConfig = Bitmap.Config.RGB_565;
- //釋放記憶體,共享引用(21版本後失效)
- options.inPurgeable = true;
- options.inInputShareable = true;
- //inJustDecodeBounds為false,返回bitmap
- options.inJustDecodeBounds = false;
- Bitmap bitmap = BitmapFactory.decodeResource(getResources(), images[position], options);
- imageView.setImageBitmap(bitmap);
- return imageView;
- }
- }
- }
5.在實際的應用過過程中,inSampleSize計算並不會那麼“理想 ”。比如ImagView的大小是100*100畫素,而圖片的原始大小是200*300呢?inSampleSize為2,則縮放後的圖片大小為100*150畫素,仍然是適合的;但是為3那麼縮小後的圖片大小就會小於ImageView的期望大小,這樣圖片就會拉伸從而導致模糊。下面我們就提供一種計算inSampleSize的計算方式,供大家參考:
- publicstaticint caculateInSampleSize(BitmapFactory.Options,int reqWidth,int reqHeight){
- finalint height = options.outHeight;
- finalint width = options.outWidth;
- int inSampleSize = 1;
- if(height > reqHeight || width > reqWidth){
- finalint halfHeight = height / 2;
- finalint halfWidth = width / 2;
- while(((halfHeight / inSampleSize) >= reqHeight) && ((halfWidth / inSampleSize) >=reqWidth)){
- inSampleSize *= 2;
- }
- }
- return inSampleSize;
- }
6.程式碼庫
QProject:https://github.com/Pengchengxiang/QProject 分支:feature/bitmapoption