Android Bitmap優化
開發中經常需要使用Bitmap進行點陣圖顯示,由於現在手機畫素的提升,直接頻繁顯示原圖會消耗大量的記憶體,很容易造成OOM,因此我們需要對Bitmap進行壓縮處理。
首先在通過BitmapFactory建立Bitmap時可以發現,官方為我們提供了一個option引數,大多數開發者都知道這個引數可以幫助我們調節Bitmap的質量,從而實現圖片的壓縮。
官方為我們提供瞭如上幾種配置資訊。public enum Config { ALPHA_8 (1), RGB_565 (3), ARGB_4444 (4), ARGB_8888 (5); }
其實這都是色彩的儲存方法:我們知道ARGB指的是一種色彩模式,裡面A代表Alpha,R表示red,G表示green,B表示blue,其實所有的可見色都是右紅綠藍組成的,所以紅綠藍又稱為三原色,每個原色都儲存著所表示顏色的資訊值。
說白了其實就是:
ALPHA_8就是Alpha由8位組成,代表8位Alpha點陣圖
ARGB_4444就是由4個4位組成即16位,代表16位ARGB點陣圖
ARGB_8888就是由4個8位組成即32位,代表32位ARGB點陣圖
RGB_565就是R為5位,G為6位,B為5位共16位,代表16位RGB點陣圖根據上面的引數可以看出,當我們需要對一個圖片進行壓縮時,如果需要其擁有透明度則選擇ARGB_4444,如果不需要透明度則選擇RGB_565。雖然這會損失一些細節但是普通顯示上是可以接受的。
除了顏色配置,還有一個引數影響著圖片大小,那就是解析度。這裡就不多說原因,相信都知道,那就直接說怎麼解決。
可以直接通過修改Bitmap大小實現:
通過Matrix對圖片按倍數放大和縮小:/** * @param bitmap 原始檔 * @param convertWidth 輸出寬度 * @param convertHeight 輸出高度 * @param option 如果option定義為 OPTIONS_RECYCLE_INPUT,則會回收bitmap原始檔,不用手動釋放 */ public static Bitmap convertBitmap(Bitmap bitmap, int convertWidth, int convertHeight,int option) { Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, convertWidth, convertHeight, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); return thumbnail; }
/**
* 講bitmap放大或縮小制定的倍數
* @param bytes bitmap的位元組陣列
* @param scale 倍數
*/
public static byte[] scaleBitmap(byte[] bytes, float scale) {
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
newBitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
byte[] byteArray = bos.toByteArray();
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
bitmap.recycle();
newBitmap.recycle();
return byteArray;
}
上面兩種都有一定的侷限性,第一個需要指定精確的長寬值,如果指定比例和原始比例不符,會導致圖片變形。第二個雖然不會變形,但是隻能按倍數增大和縮小。並且這兩種都需要先生成一個Bitmap,再通過這個Bitmap進行轉換。
最後一種,在Bitmap建立時便直接建立成想要的尺寸:
public interface CompressBitmapCallback {
void onComplete(Bitmap bitmap);
}
private static Executor executor = Executors.newFixedThreadPool(3);
/**
* 生成壓縮過的bitmap
*
* @param height 定製最大高度
* @param width 定製最大寬度
* @param path 圖片本地路徑
* @param callback 操作完成回撥
*/
public static void getCompressBitmap(final int height, final int width, final String path, final Handler handler, final CompressBitmapCallback callback) {
executor.execute(new Runnable() {
@Override
public void run() {
BitmapFactory.Options options = new BitmapFactory.Options();
//inJustDecodeBounds為true時建立的Bitmap只有尺寸資訊,而沒有真正的建立,可以節省資源
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
int bitHeight = options.outHeight;
int bitWidth = options.outWidth;
int sample = 1;
//通過原始尺寸和需要輸出尺寸判斷sample值
if (bitHeight > height || bitWidth > width) {
while (bitHeight / (2 * sample) >= height || bitWidth / (2 * sample) >= width) {
sample *= 2;
}
}
Bitmap compressBitmap = null;
options.inJustDecodeBounds = false;
options.inSampleSize = sample;
options.inPreferredConfig = Bitmap.Config.RGB_565;
compressBitmap = BitmapFactory.decodeFile(path, options);
final Bitmap finalCompressBitmap = compressBitmap;
handler.post(new Runnable() {
@Override
public void run() {
callback.onComplete(finalCompressBitmap);
}
});
}
});
}
這種轉換是比較推薦的一種方式,先將options的inJustDecodeBounds屬性設為true,得到將要生成Bitmap的尺寸(注意這裡其實系統並沒有真正生成bitmap,只是拿到了對應的尺寸資訊,因此不會消耗太多資源)。然後再通過需要輸出的大小進行計算inSampleSize的值,這裡解釋一下,這個值的大小代表了生成圖片會是原圖片大小的N分之一,如果是2則為原圖的一半,這也是官方比較推薦的一種方式,並且這樣生成的圖片因為是按比例縮放,所以不會出現圖片變形的情況。還有需要注意的是,這個值只有當為2的指數時才會生效,即1、2、4、8。具體原因不詳。
採用這種方法可以使用大多數情況,但是同樣也有缺點,那就是不能保證輸出圖片尺寸的精確性,因為縮放比例只能是2的指數,所以和想要的尺寸肯定會存在出入。但是這種方法相對來說最節省資源,並且可以儘量保證圖片的顯示效果。
通過BitmapFactory.Options來縮放圖片,主要用到inSampleSize屬性,獲得的Bitmap解析度大小將會為原圖的1/inSampleSize,其中該值必須是2的指數,即:1,2,4,8,16等,不滿足時繪選擇一個最最接近的指數來代替。
為了保證不同解析度圖片處理之後得到同樣的效果,現將Options的inJustDecodeBounds設為true,這樣生成的bitmap只有尺寸屬性而沒有真正的圖片,通過得到的尺寸來計算inSampleSize的大小,再次生成bitmap即為我們想要得到的最終Bitmap。