1. 概述
Android 中的圖片是以 Bitmap 方式存在的,繪製的時候也是 Bitmap,直接影響到app運行時的內存。通過 ImageView 來顯示圖片,很多時候 ImageView 並沒有原始圖片的尺寸那麼大,這個時候把整個圖片加載進來後再設置給 ImageView,顯然是沒有必要的,因爲 ImageView 根本沒辦法顯示原始圖片。可以將圖片縮小後再加載進來,這樣圖片既能在 ImageView 顯示出來,又能降低內存佔用從而在一定程度上避免OOM,提高了 Bitmap 加載時的性能。在Android,Bitmap 所佔用的內存計算公式是:Bitmap所佔用的內存 = 圖片長度 x 圖片寬度 x 一個像素點佔用的字節數。只要通過改變這3個參數,任意減少一個的值,就達到了壓縮的效果。
2. 單個像素點佔用的字節數
單個像素的字節大小由 Bitmap 的一個可配置的參數 Config 來決定,Config 是枚舉類,定義了 Android中 支持的 Bitmap 配置:
Config | 佔用字節大小(byte) | 說明 |
---|---|---|
ALPHA_8 | 1 | 每個像素存儲爲單個半透明(alpha)通道。 |
RGB_565 | 2 | 簡易RGB色調 |
ARGB_4444 | 4 | 已廢棄 |
ARGB_8888 | 4 | 默認圖片配置 |
RGBA_F16 | 8 | Android 8.0 新增 |
HARDWARE | Special | Android 8.0 新增 (Bitmap直接存儲在graphic memory) |
這個是理論結論,在實際使用過程中,如果設置成了其他標準。在很多情況下,是不生效的。Android系統會強行轉成使用ARGB_8888標準。
如下所示是不同 Config 下,同一張圖片的佔用的內存大小:
if (type.equals("alpha_8")) {
mConfig = Bitmap.Config.ALPHA_8;
} else if (type.equals("8888")) {
mConfig = Bitmap.Config.ARGB_8888;
} else if (type.equals("565")) {
mConfig = Bitmap.Config.RGB_565;
} else if (type.equals("f16")) {
mConfig = Bitmap.Config.RGBA_F16;
} else if (type.equals("hardware")) {
mConfig = Bitmap.Config.HARDWARE;
}
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = mConfig;
try {
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, opts);
} catch (OutOfMemoryError error) {
mBitmap = null;
}
Log.e("zzw", "config: " + type);
Log.e("zzw", "width: " + mBitmap.getWidth() + " height: " + mBitmap.getHeight());
Log.e("zzw", "size: " + mBitmap.getByteCount());
mImg.setImageBitmap(mBitmap);
log 打印:
2019-11-07 05:15:16.930 19676-19676/cn.zzw.bitmapdemo E/zzw: config: alpha_8
2019-11-07 05:15:16.930 19676-19676/cn.zzw.bitmapdemo E/zzw: width: 1313 height: 738
2019-11-07 05:15:16.930 19676-19676/cn.zzw.bitmapdemo E/zzw: size: 3875976
2019-11-07 05:15:27.835 19676-19676/cn.zzw.bitmapdemo E/zzw: config: 8888
2019-11-07 05:15:27.835 19676-19676/cn.zzw.bitmapdemo E/zzw: width: 1313 height: 738
2019-11-07 05:15:27.835 19676-19676/cn.zzw.bitmapdemo E/zzw: size: 3875976
2019-11-07 05:15:34.442 19676-19676/cn.zzw.bitmapdemo E/zzw: config: 565
2019-11-07 05:15:34.442 19676-19676/cn.zzw.bitmapdemo E/zzw: width: 1313 height: 738
2019-11-07 05:15:34.442 19676-19676/cn.zzw.bitmapdemo E/zzw: size: 1937988
2019-11-07 05:15:39.743 19676-19676/cn.zzw.bitmapdemo E/zzw: config: f16
2019-11-07 05:15:39.744 19676-19676/cn.zzw.bitmapdemo E/zzw: width: 1313 height: 738
2019-11-07 05:15:39.744 19676-19676/cn.zzw.bitmapdemo E/zzw: size: 7751952
2019-11-07 05:15:44.425 19676-19676/cn.zzw.bitmapdemo E/zzw: config: hardware
2019-11-07 05:15:44.425 19676-19676/cn.zzw.bitmapdemo E/zzw: width: 1313 height: 738
2019-11-07 05:15:44.425 19676-19676/cn.zzw.bitmapdemo E/zzw: size: 3875976
3. Bitmap 壓縮
3.1 採樣率壓縮
採樣率壓縮其原理其實也是縮放 bitamp 的尺寸,通過調節其 inSampleSize 參數,比如調節爲2,寬高會爲原來的1/2,內存變回原來的1/4。
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 2;
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg, opts);
應用場景: 先獲取 Bitmap 的寬高,以及要顯示的大小,通過比較來決定 inSampleSize 的具體大小。在獲取 Bitmap 寬高的時候,設置 inJustDecodeBounds=true,這樣創建的 Bitmap 不佔用內存的。
3.2 質量壓縮
在保持像素的前提下改變圖片的位深及透明度等,來達到壓縮圖片的目的。bitmap 圖片的大小不會改變。
BitmapFactory.Options opts = new BitmapFactory.Options();
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.timg, opts);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 50, bos);
byte[] bytes = bos.toByteArray();
mBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
應用場景:用於對圖片進行壓縮,用於分享圖片以及保存爲文件用於上傳到服務端。
3.3 縮放法壓縮
Android 中使用 Matrix 對圖像進行縮放、旋轉、平移、斜切等變換的。
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.timg);
mBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
bitmap.recycle();
bitmap = null;
應用場景:跟 3.1 中類似,3.1 中寬高同時進行縮放,這裏可以進行分佈對寬高進行縮放。
4. Bitmap 的複用
BitmapFactory.Options.inBitmap 字段,設置此字段之後解碼方法會嘗試複用一張存在的 Bitmap。Bitmap 的內存被複用,避免了內存的回收及申請過程,性能表現更佳。
BitmapFactory.Options opts1 = new BitmapFactory.Options();
opts1.inMutable=true;
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.na,opts1);
BitmapFactory.Options opts2 = new BitmapFactory.Options();
opts2.inBitmap=mBitmap;
opts2.inSampleSize = 1;
Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.timg,opts2);
mImg.setImageBitmap(bitmap2);
用了 inBitmap 這個屬性,加載兩張,這兩張圖片會指向同一塊內存,而不用開闢兩塊內存空間。
inBitmap 的限制:
3.0-4.3:複用的圖片大小必須相同,編碼必須相同;4.4以上:複用的空間大於等於即可,編碼不必相同,不支持WebP。
圖片複用,這個屬性必須設置爲true:options.inMutable = true;
5. LruCache 的使用
關於 LruCache 的使用,參考此篇:LruCache 源碼解析
附上文章中的例子: