我們通過眼睛來觀察世界,眼睛通過光的反射,折射將世間的萬物映射到我們
的眼睛裏。至於是在眼睛裏生成圖片傳遞給大腦,還是眼睛將映射得來的
這些圖片,瞭解了世界。
圖片何其重要,幾乎每一個app都需要加載圖片,然而並不是每一個app
都很好的加載了圖片。
圖片是什麼,我們先說一下圖片的屬性:形狀,大小,顏色。
這一節裏我們討論的是圖片最簡單的屬性:大小。
每一部安卓手機對於加載圖片使用的內存都是有限制的,據我所知,通常
的都是16MB。然而,就像世界上沒有一模一樣的葉子一樣。圖片也是多種
多樣的,它們大小不一,形狀各異。那麼如果超過16MB會發生什麼錯誤呢
你的手機夠舊,配置夠低,那麼很容易的就會報Out of memory的異常,也就是一些人講的OOM異常。
也許你會發現你的圖片並沒有超過16MB啊,爲何還是報錯,那是因爲手
機在加載圖片的時候需要的內存可能會比圖片本身大,(這應該是涉及到圖
片的複雜程度吧)。
如何避免OOM異常呢,手機內存的限制通過軟件是無法改變的,那麼我
們只好在圖片本身上下文章。圖片並不是一成不變的,只要我們能通過一定
的比例縮放圖片就不需要那麼大的內存了。一句話概括就是我們要儘量不破
壞原圖表達的信息的基礎上減小加載圖片所需要的內存。而BitmapFactory.Options這個類就是爲此而生的。
package com.example.loadbigbitmap;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
public class ImageUtil {
/**
*
* @param options
* 根據原圖獲得的options,通過它可以獲得原圖的高,寬,類型。因爲inJustDecodeBounds設置成true了,
* 所以並不會真的解碼生成一個bitmap,也就不存在佔用內存的問題了。
* @param reqWidth 要求的寬
* @param reqHeight 要求的高
* @return 返回設置好的inSampleSize。
*/
/*
* 根據原圖的高度和寬度和要求的高度和寬度計算出inSampleSize的大小。
* 通過設置inSampleSize可以修改顯示該圖片需要的內存大小。比如原來需要20M
* ,那麼如果inSampleSize=5,則實際顯示的時候只需要5M就夠了。
*/
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
*
* @param res 使用getResources()即可
* @param resId 圖片的id
* @param reqWidth 要求的高度
* @param reqHeight 要求的寬度
* @return 將會返回一個已經根據要求的高度和寬度縮放的bitmap
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res,
int resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
/*
* 如果我們把它設爲true,那麼BitmapFactory.decodeFile(String path, Options
* opt)並不會真的返回一個Bitmap給你,它僅僅會把它的寬,高取回來給你,這樣就不會佔用太多的內存,也就不會那麼頻繁的發生OOM了。
*/
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
// 把inJustDecodeBounds設置爲false,這樣通過BitmapFactory生成bitmap時就會返回一個真的bitmap而不是null;
options.inJustDecodeBounds = false;
//根據圖片的來源確定到底使用哪一種生成BitmapFactory的方法。
return BitmapFactory.decodeResource(res, resId, options);
}
}
MainActivity
package com.example.loadbigbitmap;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.widget.ImageView;
public class MainActivity extends Activity {
/*
* 顯示圖片
*/
private ImageView image;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intiUI();
/*
* 圖片的原始寬度,高度,類型: 1024;768;image/jpeg
*/
Bitmap bitmap = ImageUtil.decodeSampledBitmapFromResource(getResources(), R.drawable.default_bg, 100, 100);
image.setImageBitmap(bitmap);
}
private void intiUI() {
image = (ImageView) findViewById(R.id.image);
}
}
接下來我就會一步一步的從bitmap的生成到BitmapFactory.Options類的
對象的屬性的設置。來講解如何縮放大容量Bitmap。
BitmapFactory有很多方式生成Bitmap。(decodeByteArray(), decodeFile(), decodeResource(), etc.)
這裏我們使用decodeResource()方法生成bitmap,其他方法大同小異,可以根據自己的需求修改。
BitmapFactory.decodeResource(getResources(),R.drawable.default_bg, options);
第一個參數是Resources,直接getResources()就行了,第二個參數是圖片的id,第三個參數就是BitmapFactory.Options了,它通過:
final BitmapFactory.Options options = new BitmapFactory.Options();
來獲取。
通過options.inSampleSize的設置控制顯示圖片所需要的大小
整篇文章所需要做的就是設置這個值,假設inSampleSize=5.則根據這個options作爲參數,加載的圖片的內存就會是原來的1/5。
我們應該根據圖片原有的高度,寬度,與需要顯示的高度寬度的比例來計算出這個inSampleSize的大小,如何計算請參考代碼ImageUtil類裏的calculateInSampleSize方法。
假設需要顯示的高度和寬度已經知道了,如何取得圖片原有的高度和寬度呢?
這時候我們就需要先設置options.inJustDecodeBounds = true;設置成true後再執行BitmapFactory.decodeResource(res, resId, options);方法則安卓系統並不會真的去解碼生成一個bitmap,而是返回一個NULL,但是這執行完後的options就會擁有圖片的高度,寬度,類型等屬性了。
有了這個屬性,再執行
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
就對inSampleSize 的值設置成功了。
這時候再執行
// 把inJustDecodeBounds設置爲false,這樣通過BitmapFactory生成bitmap時就會返回一個真的bitmap而不是null;
options.inJustDecodeBounds = false;
//根據圖片的來源確定到底使用哪一種生成BitmapFactory的方法。
return BitmapFactory.decodeResource(res, resId, options);
便能得到一個縮小之後的bitmap。且不會再佔用很大的內存。