將大圖載入到記憶體中總是令人痛苦,因為我們經常會在應用的崩潰報告中看到OOM(Out Of Memory)的bug。大家都知道,Android系統的記憶體有限。我們必須牢記這一點。
stackoverflow上有很多關於大圖載入的問題,當你的應用程式遇到OOM的時候,你可以選擇直接複製貼上其中的答案來解決這個問題。因此,你完全可以略過本篇文章,但我想介紹一些載入大圖的基礎知識及其實際工作的原理。
我只想解釋圖片解碼背後的邏輯。我建議你使用Picasso或Glide來載入圖片。沒有必要重新發明輪子。
將圖片載入到記憶體中
這很簡單。你只需要使用BitmapFactory來解碼你的圖片。
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage);
imageView.setImageBitmap(bitmap);
複製程式碼
看起來一切正常。但是我要告訴你一個問題,讓我們看看這張解碼過的圖片在記憶體中實際佔據的空間大小。
bitmap.getByteCount()方法將返回bitmap的大小。 這張圖片在記憶體中的大小為12262248位元組,相當於12.3 MB。是的,你可能會感到困惑。因為這張圖片在磁碟上的實際大小約為3.5 MB,而getByteCount()方法返回的值遠大於它。原因如下:
儲存在磁碟上的圖片是被壓縮過的(以JPG,PNG或類似的格式儲存)。 一旦將圖片載入到記憶體中,它就不再被壓縮,並佔用儘可能多的圖片的所有畫素所需的記憶體空間。
載入大圖的步驟
- 獲取圖片的寬和高
- 根據圖片的寬和高計算縮放比
- 根據縮放比將圖片載入到記憶體中。
BitmapFactory.Options
BitmapFactory可以為我們提供圖片的後設資料。我們可以使用這個類來實現第一步。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage, options);
複製程式碼
我們將BitmapFactory.Options例項傳遞給BitmapFactory.decodeSource()方法。options.inJustDecodeBounds = true 是什麼意思?這句程式碼是指我們不想將圖片載入到記憶體中。我們只想獲取圖片的相關資訊(寬度,高度等),並使用這些資訊來計算縮放比例。
我們執行這段程式碼並列印圖片的資訊:
options.outHeight : 1126
options.outWidth : 2000
options.bitmap : null
複製程式碼
它只輸出了圖片的高度和寬度。
Reducing Image Size (In Memory)
現在我們需要計算inSampleSize。什麼是inSampleSize? inSampleSize是BitmapFactory.Options類的一個屬性,用於設定圖片的縮放比。
如果我們有一張尺寸為1000x1000的圖片,並且在解碼之前設定inSampleSize的值為2, 那麼解碼之後,我們將得到一張尺寸為500x500的圖片。 如果我們有一張尺寸為200x400的圖片,並且在解碼之前設定inSampleSize的值為5, 那麼解碼之後,我們將得到一張尺寸為40x80的圖片。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inSampleSize = 3;
BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage, options);
複製程式碼
我們可以直接這樣做嗎?不能,因為我們不知道圖片大小是多少。如果它是小圖片,並且我們使其更小,那麼我們的使用者看到的就是一些畫素而不是影象。有一些圖片需要縮放5倍,另一些圖片則需要縮放2倍。我們不能將縮放比設定為一個常數,所以我們必須根據圖片的大小來計算它的值。
如何計算inSampleSize的值取決於您。我的意思是,你可以根據你的需要編寫inSampleSize的計算方法。在android官方文件中,計算結果是2的冪次方。
options.inSampleSize = calculateInSampleSize(options, 500,500);
options.inJustDecodeBounds = false;
Bitmap smallBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage, options);
複製程式碼
這裡我們將inJustDecodeBounds的值設為false,並獲得了一個bitmap物件。現在,bitmap.getByteCount()方法返回的圖片大小是3.1 MB。這是它在記憶體中的大小。正如我之前說過的,圖片儲存在磁碟上時會被壓縮。當我們將它們載入到記憶體中時它們會佔據更大的記憶體空間。通過上面這種方法,我們將它在記憶體中佔據的空間大小從12.3 MB減少到了3.1 MB,減少了75%。
Reducing Image Size (In Disk)
我們還可以使用Bitmap的compress方法對磁碟上的圖片進行壓縮。
我們來看看在不改變圖片質量的情況下圖片被壓縮後的大小。 100表示與原圖保持相同的質量。
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
byte[] bitmapdata = bos.toByteArray();
複製程式碼
通過計算得到圖片在磁碟上的大小為1.6 MB。
我們把compress方法中的質量引數改為50,並再次計算圖片大小。
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, bos);
複製程式碼
通過計算得到圖片在磁碟上的大小為24.4 KB。
注意:在改變compress方法中的質量引數的時候,壓縮格式應該是.JPEG。設定為PNG格式的時候,修改是無效的。
下面是一張對比效果圖: