關(guān)于Bitmap加載
Bitmap的加載離不開BitmapFactory類,BitmapFactory類提供了四類方法用來加載Bitmap:
- 第一種: BitmapFactory.decodeFile,從文件系統(tǒng)加載
a. 通過Intent打開本地圖片或照片
b. 在onActivityResult中獲取圖片uri
c. 根據(jù)uri獲取圖片的路徑
d. 根據(jù)路徑解析bitmap:Bitmap bm = BitmapFactory.decodeFile(sd_path) - 第二種:BitmapFactory.decodeResource,以R.drawable.xxx的形式從本地資源中加載
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.aaa); - 第三種:BitmapFactory.decodeStream,從輸入流加載
a.開啟異步線程去獲取網(wǎng)絡(luò)圖片
b.網(wǎng)絡(luò)返回InputStream
c.解析:Bitmap bm = BitmapFactory.decodeStream(stream),這是一個耗時操作,要在子線程中執(zhí)行 - 第四種:BitmapFactory.decodeByteArray, 從字節(jié)數(shù)組中加載
a.開啟異步線程去獲取網(wǎng)絡(luò)圖片
b.網(wǎng)絡(luò)返回InputStream
c. 把InputStream轉(zhuǎn)換成byte[]
d. 解析:Bitmap bm = BitmapFactory.decodeByteArray(myByte,0,myByte.length);
關(guān)于Bitmap的優(yōu)化
提到bitmap,我們通常會聯(lián)想到內(nèi)存溢出,主要的原因就是我們加載的圖片內(nèi)存過大以及每個應(yīng)用的內(nèi)存有限。所以我們經(jīng)常會看到報這種錯誤:
Bitmap too large to be uploaded into a texture (3120x4160, max=4096x4096)
或
Caused by: java.lang.OutOfMemoryError: Failed to allocate a 144764940 byte allocation with 16765264 free bytes and 109MB until OOM
一、Bitmap優(yōu)化之高效加載---尺寸壓縮
主要的做法就是使用系統(tǒng)提供給我們Options類來處理Bitmap。
通過BitmapFactory.Options按一定的采樣率來加載縮小后的圖片,然后在ImageView中使用縮小的圖片這樣就會降低內(nèi)存占用避免【OOM】,提高了Bitamp加載時的性能。
這其實就是我們常說的圖片壓縮方案之尺寸壓縮。
尺寸壓縮是壓縮圖片的像素,一張圖片所占內(nèi)存的大小 =【圖片類型】(ALPHA_8/ARGB_4444/ARGB_8888/RGB_256)*【寬】*【高】,通過改變?nèi)齻€值減小圖片所占的內(nèi)存,防止OOM,當(dāng)然這種方式可能會使圖片失真 。
對于上面提到的圖片類型,也就是android 色彩模式說明:
- ALPHA_8:每個像素占用1byte內(nèi)存。
- ARGB_4444:每個像素占用2byte內(nèi)存
- ARGB_8888:每個像素占用4byte內(nèi)存
- RGB_565:每個像素占用2byte內(nèi)存
而android默認(rèn)的色彩模式就是ARGB_8888,質(zhì)量最高,同時內(nèi)存也是最大。效果也肯定是最好的。
假設(shè)一張10241024,模式為ARGB_8888的圖片,那么它占有的內(nèi)存就是:10241024*4 = 4MB
Options類的inPreferredConfig
用這個改變內(nèi)存大小的三個值得第一個。圖片類型,指定為更小的色彩模式,可以讓圖片的內(nèi)存變小。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig= Bitmap.Config.RGB_565;
Options類的inSampleSize采樣率
options.inPreferredConfig=2;
通過采樣率改變內(nèi)存大小的三個值中的寬和高這兩個值,采樣率同時作用于寬和高。
以下有幾點注意事項:
- 當(dāng)inSampleSize=1時,采樣后的圖片為圖片的原始大小。
- 除了1以外,inSampleSize的取值應(yīng)該總為2的整數(shù)倍,否則會【向下取整】,取一個最接近2的整數(shù)倍,比如inSampleSize=3時,系統(tǒng)會取inSampleSize=2
- 當(dāng)inSampleSize=2時,采樣后的圖片的寬高均為原始圖片寬高的1/2
假設(shè)一張10241024,模式為ARGB_8888的圖片,inSampleSize=2,原始占用內(nèi)存大小是4MB,采樣后的圖片占用內(nèi)存大小就是(1024/2) * (1024/2 ) 4 = 1MB
我們用一個小例子來驗證上述講的:
代碼如下:
public class MainActivity extends AppCompatActivity {
ImageView image;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main_test);
image= (ImageView) findViewById(R.id.image);
//獲取我們要顯示的圖片
Bitmap bm = decodeBitmapFromResource();
//通過imageview顯示出來
image.setImageBitmap(bm);
}
//在這個方法里我們對圖片做處理
private Bitmap decodeBitmapFromResource(){
Log.d("log","bitmap原始圖片內(nèi)存大?。?+BitmapFactory.decodeResource(getResources(), R.drawable.tupian).getAllocationByteCount());
//獲取 BitmapFactory.Options的實例
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig= Bitmap.Config.RGB_565;
//inJustDecodeBounds參數(shù)設(shè)為true并加載圖片。
//這樣做的原因是inJustDecodeBounds為true時
//BitmapFactory只會解析圖片的原始寬高信息,并不會真正的加載圖片。
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.tupian, options);
Log.d("log","原始圖片的寬width:" +options.outWidth+"原始圖片的高h(yuǎn)eight:"+options.outHeight);
// Log.d("log","bitmap原始圖片內(nèi)存大小:"+ bitmap.getAllocationByteCount());
//原始圖片的寬高已經(jīng)存放在options中。options.outWidth和options.outHeight
//我們可以用它去做一些判斷,得到我們想要的采樣率
//options.inSampleSize = calculateSampleSize(options,300,300);
options.inSampleSize=2;
//得到采樣率后,將BitmapFacpry.Options的inSampleSize參數(shù)設(shè)為false并重新加載圖片。
//此時是真正的加載圖片。
options.inJustDecodeBounds =false;
Bitmap newbitmap=BitmapFactory.decodeResource(getResources(),R.drawable.tupian,options);
Log.d("log","壓縮后圖片的寬width:" +options.outWidth+"壓縮后圖片的高h(yuǎn)eight:"+options.outHeight);
Log.d("log","bitmap壓縮后圖片內(nèi)存大小:"+ newbitmap.getByteCount());
return BitmapFactory.decodeResource(getResources(),R.drawable.tupian,options);
}
}


根據(jù)輸出,我們可得到?jīng)]有對圖片處理前圖片類型:
1411200/600/588=4;也就是android默認(rèn)的色彩模式就是ARGB_8888,4byte
而代碼中我們通過設(shè)置這兩個值
options.inPreferredConfig= Bitmap.Config.RGB_565;
options.inSampleSize=2;
使得類型為RGB_565--2byte,同時寬高同時為原來的1/2,此時圖片的內(nèi)存為=2* 300 *294=176400;
二、Bitmap優(yōu)化之日常使用注意事項
【1】對于不再使用的bitmap,要及時清理回收,Activity的onStop()或者onDestroy()方法中進(jìn)行回收: bitmap.recycle(); //回收圖片所占的內(nèi)存
【2】捕獲OutOfMemoryError
因為Bitmap非常耗內(nèi)存,了避免應(yīng)用在分配Bitmap內(nèi)存的時候出現(xiàn)OutOfMemory異常以后Crash掉,需要特別注意實例化Bitmap部分的代碼。通常,在實例化Bitmap的代碼中,一定要對OutOfMemory異常進(jìn)行捕獲。很多開發(fā)者會習(xí)慣性的在代碼中直接捕獲Exception。但是對于OutOfMemoryError來說,這樣做是捕獲不到的。因為OutOfMemoryError是一種Error,而不是Exception。
Bitmap bitmap = null;
try {
// 實例化Bitmap
bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
}
if (bitmap == null) {
return defaultBitmapMap; // 如果實例化失敗 返回默認(rèn)的Bitmap對象
}
【3】不重復(fù)創(chuàng)建相同的bitmap
如果不進(jìn)行緩存,盡管看到的是同一張圖片文件,但是使用BitmapFactory類的方法來實例化出來的Bitmap,是不同的Bitmap對象。緩存可以避免新建多個Bitmap對象,避免內(nèi)存的浪費。
如果我們的程序中要創(chuàng)建多個bitmap,比如某個場景,我們要在屏幕上灑各式各樣的花瓣,但這其中總會有重復(fù)的,這里我們就可以使用一個hashmap,對每個bitmap以一個值作為標(biāo)識,然后在創(chuàng)建函數(shù)中進(jìn)行判斷,
//簡易代碼,大概表達(dá)思想:
HashMap<Integer, Bitmap> bitmapMap = new HashMap<Integer, Bitmap>();
static Bitmap createBitmap(int value){
Bitmap bitmap = bitmapMap.get(value);
if (bitmap == null) {
bitmap =BitmapFactory.decodeResource(getResources(), R.drawable.value);
bitmapMap.put(value, bitmap);
}
return bitmap;
}