Android高效加載大圖、多圖解決方案,有效避免程序OOM
高效加載大圖片我們在編寫(xiě)Android程序的時(shí)候經(jīng)常要用到許多圖片,不同圖片總是會(huì )有不同的形狀、不同的大小,但在大多數情況下,這些圖片都會(huì )大于我們程序所需要的大小。比如說(shuō)系統圖片庫里展示的圖片大都是用手機攝像頭拍出來(lái)的,這些圖片的分辨率會(huì )比我們手機屏幕的分辨率高得多。大家應該知道,我們編寫(xiě)的應用程序都是有一定內存限制的,程序占用了過(guò)高的內存就容易出現OOM(OutOfMemory)異常。我們可以通過(guò)下面的代碼看出每個(gè)應用程序最高可用內存是多少。
本文引用地址:http://dyxdggzs.com/article/201809/388514.htmint maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
Log.d(TAG, Max memory is + maxMemory + KB);
復制代碼
因此在展示高分辨率圖片的時(shí)候,最好先將圖片進(jìn)行壓縮。壓縮后的圖片大小應該和用來(lái)展示它的控件大小相近,在一個(gè)很小的ImageView上顯示一張超大的圖片不會(huì )帶來(lái)任何視覺(jué)上的好處,但卻會(huì )占用我們相當多寶貴的內存,而且在性能上還可能會(huì )帶來(lái)負面影響。下面我們就來(lái)看一看,如何對一張大圖片進(jìn)行適當的壓縮,讓它能夠以最佳大小顯示的同時(shí),還能防止OOM的出現。
BitmapFactory這個(gè)類(lèi)提供了多個(gè)解析方法(decodeByteArray, decodeFile, decodeResource等)用于創(chuàng )建Bitmap對象,我們應該根據圖片的來(lái)源選擇合適的方法。比如SD卡中的圖片可以使用decodeFile方法,網(wǎng)絡(luò )上的圖片可以使用decodeStream方法,資源文件中的圖片可以使用decodeResource方法。這些方法會(huì )嘗試為已經(jīng)構建的bitmap分配內存,這時(shí)就會(huì )很容易導致OOM出現。為此每一種解析方法都提供了一個(gè)可選的BitmapFactory.Options參數,將這個(gè)參數的inJustDecodeBounds屬性設置為true就可以讓解析方法禁止為bitmap分配內存,返回值也不再是一個(gè)Bitmap對象,而是null。雖然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType屬性都會(huì )被賦值。這個(gè)技巧讓我們可以在加載圖片之前就獲取到圖片的長(cháng)寬值和MIME類(lèi)型,從而根據情況對圖片進(jìn)行壓縮。如下代碼所示:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
復制代碼
為了避免OOM異常,最好在解析每張圖片的時(shí)候都先檢查一下圖片的大小,除非你非常信任圖片的來(lái)源,保證這些圖片都不會(huì )超出你程序的可用內存?,F在圖片的大小已經(jīng)知道了,我們就可以決定是把整張圖片加載到內存中還是加載一個(gè)壓縮版的圖片到內存中。以下幾個(gè)因素是我們需要考慮的:
預估一下加載整張圖片所需占用的內存。
為了加載這一張圖片你所愿意提供多少內存。
用于展示這張圖片的控件的實(shí)際大小。
當前設備的屏幕尺寸和分辨率。
比如,你的ImageView只有128*96像素的大小,只是為了顯示一張縮略圖,這時(shí)候把一張1024*768像素的圖片完全加載到內存中顯然是不值得的。那我們怎樣才能對圖片進(jìn)行壓縮呢?通過(guò)設置BitmapFactory.Options中inSampleSize的值就可以實(shí)現。比如我們有一張2048*1536像素的圖片,將inSampleSize的值設置為4,就可以把這張圖片壓縮成512*384像素。原本加載這張圖片需要占用13M的內存,壓縮后就只需要占用0.75M了(假設圖片是ARGB_8888類(lèi)型,即每個(gè)像素點(diǎn)占用4個(gè)字節)。下面的方法可以根據傳入的寬和高,計算出合適的inSampleSize值:
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// 源圖片的高度和寬度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// 計算出實(shí)際寬高和目標寬高的比率
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// 選擇寬和高中最小的比率作為inSampleSize的值,這樣可以保證最終圖片的寬和高
// 一定都會(huì )大于等于目標的寬和高。
inSampleSize = heightRatio widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
復制代碼
使用這個(gè)方法,首先你要將BitmapFactory.Options的inJustDecodeBounds屬性設置為true,解析一次圖片。然后將BitmapFactory.Options連同期望的寬度和高度一起傳遞到到calculateInSampleSize方法中,就可以得到合適的inSampleSize值了。之后再解析一次圖片,使用新獲取到的inSampleSize值,并把inJustDecodeBounds設置為false,就可以得到壓縮后的圖片了。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// 第一次解析將inJustDecodeBounds設置為true,來(lái)獲取圖片大小
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 調用上面定義的方法計算inSampleSize值
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 使用獲取到的inSampleSize值再次解析圖片
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
復制代碼
下面的代碼非常簡(jiǎn)單地將任意一張圖片壓縮成100*100的縮略圖,并在ImageView上展示。
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
評論