背景
Android開發中,加載圖片過多、過大很容易引起OutOfMemoryError例外,即我們常見的記憶體溢位,因為Android對單個應用施加記憶體限制,默認分配的記憶體只有幾M(具體視不同系統而定),而載入的圖片如果是JPG之類的壓縮格式(JPG支持最高級別的壓縮,不過該壓縮是有損的),在記憶體中展開會占用大量的記憶體空間,也就容易形成記憶體溢位,那么高效的加載Bitmap是很重要的事情,Bitmap在Android中指的是一張圖片,圖片的格式有.jpg .png .webp 等常見的格式,
如何選擇圖片格式
一個原則: 在保證圖片視覺不失真前提下,盡可能的縮小體積
Android目前常用的圖片格式有png,jpeg和webp
- png:無損壓縮圖片格式,支持Alpha通道,Android切圖素材多采用此格式
- jpeg:有損壓縮圖片格式,不支持背景透明,適用于照片等色彩豐富的大圖壓縮,不適合logo
- webp:是一種同時提供了有損壓縮和無損壓縮的圖片格式,派生自視頻編碼格式VP8,從谷歌官網來看,無損webp平均比png小26%,有損的webp平均比jpeg小25%~34%,無損webp支持Alpha通道,有損webp在一定的條件下同樣支持,有損webp在Android4.0(API 14)之后支持,無損和透明在Android4.3(API18)之后支持
采用webp能夠在保持圖片清晰度的情況下,可以有效減小圖片所占有的磁盤空間大小
圖片壓縮
圖片壓縮可以從三個方面去考慮:
-
質量
質量壓縮并不會改變圖片在記憶體中的大小,僅僅會減小圖片所占用的磁盤空間的大小,因為質量壓縮不會改變圖片的解析度,而圖片在記憶體中的大小是根據widthheight一個像素的所占用的位元組數計算的,寬高沒變,在記憶體中占用的大小自然不會變,質量壓縮的原理是通過改變圖片的位深和透明度來減小圖片占用的磁盤空間大小,所以不適合作為縮略圖,可以用于想保持圖片質量的同時減小圖片所占用的磁盤空間大小,另外,由于png是無損壓縮,所以設定quality無效,
/**
* 質量壓縮
*
* @param format 圖片格式 jpeg,png,webp
* @param quality 圖片的質量,0-100,數值越小質量越差
*/
public static void compress(Bitmap.CompressFormat format, int quality) {
File sdFile = Environment.getExternalStorageDirectory();
File originFile = new File(sdFile, "originImg.jpg");
Bitmap originBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
originBitmap.compress(format, quality, bos);
try {
FileOutputStream fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
fos.write(bos.toByteArray());
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
-
采樣率
采樣率壓縮是通過設定BitmapFactory.Options.inSampleSize,來減小圖片的解析度,進而減小圖片所占用的磁盤空間和記憶體大小,
設定的inSampleSize會導致壓縮的圖片的寬高都為1/inSampleSize,整體大小變為原始圖片的inSampleSize平方分之一,當然,這些有些注意點:
-
inSampleSize小于等于1會按照1處理
-
inSampleSize只能設定為2的平方,不是2的平方則最侄訓減小到最近的2的平方數,如設定7會按4進行壓縮,設定15會按8進行壓縮,
-
/**
*
* @param inSampleSize 可以根據需求計算出合理的inSampleSize
*/
public static void compress(int inSampleSize) {
File sdFile = Environment.getExternalStorageDirectory();
File originFile = new File(sdFile, "originImg.jpg");
BitmapFactory.Options options = new BitmapFactory.Options();
//設定此引數是僅僅讀取圖片的寬高到options中,不會將整張圖片讀到記憶體中,防止oom
options.inJustDecodeBounds = true;
Bitmap emptyBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath(), options);
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
Bitmap resultBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath(), options);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
try {
FileOutputStream fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
fos.write(bos.toByteArray());
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
-
縮放
通過減少圖片的像素來降低圖片的磁盤空間大小和記憶體大小,可以用于快取縮略圖
/**
* 縮放bitmap
* @param context
* @param id
* @param maxW
* @param maxH
* @return
*/
public static Bitmap resizeBitmap(Context context,int id,int maxW,int maxH,boolean hasAlpha,Bitmap reusable){
Resources resources = context.getResources();
BitmapFactory.Options options = new BitmapFactory.Options();
// 只解碼出 outxxx引數 比如 寬、高
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources,id,options);
//根據寬、高進行縮放
int w = options.outWidth;
int h = options.outHeight;
//設定縮放系數
options.inSampleSize = calcuteInSampleSize(w,h,maxW,maxH);
if (!hasAlpha){
options.inPreferredConfig = Bitmap.Config.RGB_565;
}
options.inJustDecodeBounds = false;
//設定成能復用
options.inMutable=true;
options.inBitmap=reusable;
return BitmapFactory.decodeResource(resources,id,options);
}
/**
* 計算縮放系數
* @param w
* @param h
* @param maxW
* @param maxH
* @return 縮放的系數
*/
private static int calcuteInSampleSize(int w,int h,int maxW,int maxH) {
int inSampleSize = 1;
if (w > maxW && h > maxH){
inSampleSize = 2;
//回圈 使寬、高小于 最大的寬、高
while (w /inSampleSize > maxW && h / inSampleSize > maxH){
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
使用JPEG庫,在jni層使用哈夫曼演算法去壓縮圖片
Android的圖片引擎使用的是閹割版的skia引擎,去掉了圖片壓縮中的哈夫曼演算法
void write_JPEG_file(uint8_t *data, int w, int h, jint q, const char *path) {
// 3.1、創建jpeg壓縮物件
jpeg_compress_struct jcs;
//錯誤回呼
jpeg_error_mgr error;
jcs.err = jpeg_std_error(&error);
//創建壓縮物件
jpeg_create_compress(&jcs);
// 3.2、指定存盤檔案 write binary
FILE *f = fopen(path, "wb");
jpeg_stdio_dest(&jcs, f);
// 3.3、設定壓縮引數
jcs.image_width = w;
jcs.image_height = h;
//bgr
jcs.input_components = 3;
jcs.in_color_space = JCS_RGB;
jpeg_set_defaults(&jcs);
//開啟哈夫曼功能
jcs.optimize_coding = true;
jpeg_set_quality(&jcs, q, 1);
// 3.4、開始壓縮
jpeg_start_compress(&jcs, 1);
// 3.5、回圈寫入每一行資料
int row_stride = w * 3;//一行的位元組數
JSAMPROW row[1];
while (jcs.next_scanline < jcs.image_height) {
//取一行資料
uint8_t *pixels = data + jcs.next_scanline * row_stride;
row[0]=pixels;
jpeg_write_scanlines(&jcs,row,1);
}
// 3.6、壓縮完成
jpeg_finish_compress(&jcs);
// 3.7、釋放jpeg物件
fclose(f);
jpeg_destroy_compress(&jcs);
}
因為涉及到jni部分,暫時只貼一下使用的代碼,后面會寫一些jni部分的博客與大家分享,
設定圖片可以復用
圖片復用主要就是指的復用記憶體塊,不需要在重新給這個bitmap申請一塊新的記憶體,避免了一次記憶體的分配和回收,從而改善了運行效率,
需要注意的是inBitmap只能在3.0以后使用,2.3上,bitmap的資料是存盤在native的記憶體區域,并不是在Dalvik的記憶體堆上,
使用inBitmap,在4.4之前,只能重用相同大小的bitmap的記憶體區域,而4.4之后你可以重用任何bitmap的記憶體區域,只要這塊記憶體比將要分配記憶體的bitmap大就可以,這里最好的方法就是使用LRUCache來快取bitmap,后面來了新的bitmap,可以從cache中按照api版本找到最適合重用的bitmap,來重用它的記憶體區域,
BitmapFactory.Options options = new BitmapFactory.Options();
// 只解碼出 outxxx引數 比如 寬、高
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources,id,options);
//根據寬、高進行縮放
int w = options.outWidth;
int h = options.outHeight;
//設定縮放系數
options.inSampleSize = calcuteInSampleSize(w,h,maxW,maxH);
if (!hasAlpha){
options.inPreferredConfig = Bitmap.Config.RGB_565;
}
options.inJustDecodeBounds = false;
//設定成能復用
options.inMutable=true;
options.inBitmap=reusable;
使用圖片快取
android中有一個LruCache是基于最記最少使用演算法實作的一個執行緒安全的資料快取類,當超出設定的快取容量時,優先淘汰最近最少使用的資料LruCache的LRU快取策略是利用LinkedHashMap來實作的,并通過封裝get/put等相關方法來實作控制快取大小以及淘汰元素,但不支持為null的key和value, 我們可以使用JakeWharton提供的一個開源庫github.com/JakeWharton… 來實作我們圖片快取的邏輯

省略了記憶體和磁盤的部分,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/348432.html
標籤:其他
