翻譯自 [某大神在Stack Overflow里的自問自答](http://stackoverflow.com/questions/32121058/most-memory-efficient-way-to-resize-bitmaps-on-android) (一般我們將Bitmap翻譯為位圖,但為了更好理解,在本文中我將它翻譯成圖像);
我們在開發的時候,經常需要從服務器中加載圖像到客戶端中,但有時手機屏幕較小(服務器傳來的圖像是大圖)導致我們需要重新調整圖像的大小以適應手機的屏幕。我們可以使用createScaledBitmap方法來調整圖像的大小,可當我們使用createScaledBitmap來得到大量的縮略圖后(圖像數量較大),會導致許多的內存溢出錯誤(out-of-memory errors)。那么問題來了,在Android中哪種方式是調整圖像大小的最有效的內存的利用方式呢?
文章Loading large bitmaps Efficiently(現在打不開,你懂的) 介紹了怎樣利用isSampleSize去加載一個圖像的縮略圖,這里只是對它進行一個總結;文章Pre-scaling bitmaps(現在打不開,你懂的) 詳細介紹了調整圖像大小的各種方法,並且怎樣去混合使用這些方法得到一個最好的內存利用方式;
在Android中有三種主要的方式來調整圖像的大小,並且每種方法會有不同的內存性能:
1. createScaledBitmap API
這個API會加載一個已經存在的圖像,並用你希望得到的圖像尺寸來創建一個新的圖像。一方面,你可以得到你想得到的確切尺寸的圖像。但這個API可以正常工作的前提是已經有一個圖像(大圖)存在了。這意味着在創建新尺寸的圖像前,原來的圖像會先經歷加載,解碼,創建的過程(在內存中創建這個大圖)。這是理想的得到確切圖像尺寸的方式,但這是以額外的內存開銷為代價的。
2. inSampleSize 屬性
BitmapFactory.Options的屬性inSampleSize在解碼時就會重新調整圖像的大小,避免為臨時的圖像進行解碼操作。在加載圖像時,會使用一個整形值x來加載原圖1/x的圖像。例如,設置inSampleSize的值為2,則會返回一個1/2原圖大小的圖像,設置inSampleSize的值為4,則會返回一個1/4原圖大小的圖像。一般來說,圖像的大小會比原圖尺寸小2的某次方;
從內存性能的角度,使用inSampleSize是最快的一種方式,因為它只解碼原圖的1/x像素到最終的圖像里。inSampleSize也有兩個主要的問題:
- 它不會給你一個確切的分辨率,它只會減小原圖的2的某次方大小;
- 它不會產生重新調整后的最好的圖像質量,大部分的調整過濾器都會通過讀取像素塊,並根據權重來得到調整后的像素。但inSampleSize僅僅是每隔幾個像素讀取一個像素來保證高性能,低內存,但圖像的質量可能就沒有那么好。
如果你只是想得到原圖的某個比例的圖像,但對圖像的質量沒什么要求,這種方法的最高效的內存利用方式;
3. inScaled, inDensity, inTargetDensity 屬性
如果你想得到的圖像的尺寸並不是原圖的2的某次方之一($1/2^x$),那么你就需要BitmapFactory.Options的這些屬性inScaled, inDensity, inTargetDensity。如果設置了inScaled屬性,系統就會通過inDensity的值和inTargetDensity的值來得到新圖的尺寸並用這個尺寸來創建新圖。如:
mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity = dstWidth;
// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(),
mImageIDs, mBitmapOptions);
使用這個方法可以得到較好圖像質量的縮略圖,因為在調整的過程中,會運用圖像過濾器(也就是某些數字方法來補償)來讓圖像看起來更好。但需要注意的是:額外的過濾補償,會帶來額外的處理時間,這個時間在處理大圖像時會快速增強,會導致調整的時間變慢,並且過濾器本身也需要額外的內存分配。
因此,如果原圖比你希望得到的圖像的大小大太多的話,這個方法並不會是比較好的選擇,因為它需要額外的過濾補償過程;
4. 混合使用這些方法
從內存和性能的角度考慮,我們可以考慮混合使用這些方法來得到一個最好的結果(設置inSampleSize,inScaled, inDensity, inTargetDensity 屬性)。
首先設置inSampleSize比希望得到的圖像尺寸的2的某次方大(如:希望得到一個原圖1/4大小的圖像,則設置inSampleSize的值為2,這些就會先得到原圖1/2大小的圖像)。然后通過設置inDensity, inTargetDensity屬性來精確需要得到圖像的尺寸,並使用過濾器來處理圖像(讓圖像變得更好看)。
混合使用這兩個方法是比較快速的操作,因為inSampleSize操作會減小后面操作的像素。如:
mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity = dstWidth * mBitmapOptions.inSampleSize;
// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);
因此如果你需要得到一個精確尺寸,並且圖像質量還可以的圖像,這個方法是一個不錯的選擇。
5. 得到圖像的尺寸
為了調整圖像的大小在不解碼原圖的情況下得到原圖的尺寸。通過設置inJustDecodeBounds屬性來幫助你得到原圖的尺寸;如:
// Decode just the boundries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;
//now go resize the image to the size you want
你可以使用這個屬性來先得到原圖的尺寸,然后計算得到目標圖像的具體值;