[譯] 在Android中高效的加載大圖

將大圖加載到內存中老是使人痛苦,由於咱們常常會在應用的崩潰報告中看到OOM(Out Of Memory)的bug。你們都知道,Android系統的內存有限。咱們必須牢記這一點。android

stackoverflow上有不少關於大圖加載的問題,當你的應用程序遇到OOM的時候,你能夠選擇直接複製粘貼其中的答案來解決這個問題。所以,你徹底能夠略過本篇文章,但我想介紹一些加載大圖的基礎知識及其實際工做的原理。bash

我只想解釋圖片解碼背後的邏輯。我建議你使用Picasso或Glide來加載圖片。沒有必要從新發明輪子。markdown

將圖片加載到內存中

這很簡單。你只須要使用BitmapFactory來解碼你的圖片。ide

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.hqimage);
imageView.setImageBitmap(bitmap);
複製代碼

看起來一切正常。可是我要告訴你一個問題,讓咱們看看這張解碼過的圖片在內存中實際佔據的空間大小。oop

bitmap.getByteCount()方法將返回bitmap的大小。 這張圖片在內存中的大小爲12262248字節,至關於12.3 MB。是的,你可能會感到困惑。由於這張圖片在磁盤上的實際大小約爲3.5 MB,而getByteCount()方法返回的值遠大於它。緣由以下:spa

存儲在磁盤上的圖片是被壓縮過的(以JPG,PNG或相似的格式存儲)。 一旦將圖片加載到內存中,它就再也不被壓縮,並佔用儘量多的圖片的全部像素所需的內存空間。code

加載大圖的步驟

  • 獲取圖片的寬和高
  • 根據圖片的寬和高計算縮放比
  • 根據縮放比將圖片加載到內存中。

BitmapFactory.Options

BitmapFactory能夠爲咱們提供圖片的元數據。咱們可使用這個類來實現第一步。orm

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格式的時候,修改是無效的。

下面是一張對比效果圖:

相關文章
相關標籤/搜索