Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Bitmap的加載簡單優化

Bitmap的加載簡單優化

編輯:關於Android編程

Bitmap如何加載

Bitmap可以認為是Android系統將圖片加載GPU的一個映射,Android可以讀取png格式的,也可以讀取jpg格式的。那麼Android是如何加載一張圖片的呢?有個類叫做BitmapFactory,它提供了四個方法:decodeFile(從文件系統中加載),decodeResource(從資源中加載),decodeStream(從輸入流中加載),decodeByteArray(從字節數組中加載);其中decodeFile和decodeResource又間接調用了decodeStream方法;

下面是decodeFile的代碼:

public static Bitmap decodeFile(String pathName, Options opts) {
        Bitmap bm = null;
        InputStream stream = null;
        try {
            stream = new FileInputStream(pathName);
            bm = decodeStream(stream, null, opts);
        } catch (Exception e) {
            /*  do nothing.
                If the exception happened on open, bm will be null.
            */
            Log.e("BitmapFactory", "Unable to decode stream: " + e);
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    // do nothing here
                }
            }
        }
        return bm;
    }

下面是decodeResource的代碼:

    public static Bitmap decodeResource(Resources res, int id, Options opts) {
        Bitmap bm = null;
        InputStream is = null; 

        try {
            final TypedValue value = new TypedValue();
            is = res.openRawResource(id, value);

            bm = decodeResourceStream(res, value, is, null, opts);
        } catch (Exception e) {
            /*  do nothing.
                If the exception happened on open, bm will be null.
                If it happened on close, bm is still valid.
            */
        } finally {
            try {
                if (is != null) is.close();
            } catch (IOException e) {
                // Ignore
            }
        }

        if (bm == null && opts != null && opts.inBitmap != null) {
            throw new IllegalArgumentException("Problem decoding into existing bitmap");
        }

        return bm;
    }

其中的decodeResourceStream方法是這樣的:

    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) {

        if (opts == null) {
            opts = new Options();
        }

        if (opts.inDensity == 0 && value != null) {
            final int density = value.density;
            if (density == TypedValue.DENSITY_DEFAULT) {
                opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
            } else if (density != TypedValue.DENSITY_NONE) {
                opts.inDensity = density;
            }
        }

        if (opts.inTargetDensity == 0 && res != null) {
            opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
        }

        return decodeStream(is, pad, opts);
    }

最後依然調用的是decodeStream方法。

這幾個方法是在底層實現的,對應著幾個native方法,這裡不說了。

BitmapFactory.Option的幾個參數簡介

我們都知道在Android裡內存溢出的最大元凶就是圖片,如何避免這種情況呢?其實也挺簡單,就是采用BitmapFactory.Option這個對象設置圖片顯示的尺寸。

我們一般顯示圖片會使用ImageView等控件,但是它們需要的尺寸一般都會比我們提供的圖片的尺寸下,所以我們要對圖片壓縮,下面我們就看一下BitmapFactory.Option提供的方法。

屬性 說明 int inSampleSize 字面意思為取樣的尺寸,為int 型;雖然它只設定一個值,但是它會影響到解析圖片為Bitmap時長寬兩個方向上的像素大小。它設定值會使Bitmap按倍數縮小。默認為1,最小值也為1,表示不縮小;當大於1時,它才會按照比例縮小;比如我們設置為2,Bitmap的長寬會變為原大小的一半,那麼相應的它的像素所占有的內存就會縮小為原來的1/4。需要注意的是它的值必須為2的n次冪,例如:1,2,4,8,16 boolean inJustDecodeBounds 字面意思為只解析Bitmap邊界,也就是長寬;為Boolean型,當我們設置為true時,我們使用BitmapFactory的decode方式解析圖片時就不會返回Bitmap,而只會讀取該圖片的尺寸和類型信息。我們一般通過這個方法來獲取尺寸,然後再按照其尺寸縮小。當然用完記得重新置為false。 Bitmap.Config inPreferredConfig 字面為Bitmap優先的設置,它是一個enum,它設置的是Bitmap的像素類型;默認為ARGB_8888,這個後面再具體介紹。 int outHeight Bitmap 的高 int outWidth Bitmap 的寬 String outMimeType 圖片的MIME類型,比如“image/jpeg”

上面的只是其中的一部分,但是已經夠用了。下面我們通過實例來看一下它的使用。

如何利用inSampleSize壓縮圖片

我們上面說過,我們不想加載圖片的原尺寸,我們需要對它進行尺寸的壓縮。那麼我們一般的步驟是什麼樣的呢?

獲取圖片尺寸 計算Bitmap合適的尺寸,即計算inSampleSize的值 設置inSampleSize,壓縮Bitmap,並顯示在ImageView上

下面我們根據這個步驟來實現一下,這裡我隨便在網上找了了一張圖片,放到了Android的外部存儲設備中,我們直接讀取這個圖片,它的像素為1024?×?683,格式為”jpg“。

首先我們不壓縮,看看什麼情況:

        Bitmap b = BitmapFactory.decodeFile(getBeautyPath());
        ivBeauty.setImageBitmap(b);

\

上面的ImageView寬高都為wrap_content,然後我們開始壓縮之旅。

1. 獲取尺寸

BitmapFactory.Options options = new BitmapFactory.Options();
//這裡我獲取圖片的尺寸和類型信息
options.inJustDecodeBounds = true;
//我們這裡獲取到的Bitmap是空的,圖片沒有真正的加載到內存,只有尺寸和類型信息
Bitmap bitmap = BitmapFactory.decodeFile(getBeautyPath(), options);
//為了確定為空,我們做一下判斷
if (bitmap == null) {
    loge("圖片的MIME類型 = " + options.outMimeType);
    loge("圖片的高 = " + options.outHeight);
    loge("圖片的寬 = " + options.outWidth);
}

上面的loge方法為我自己封裝的,調用的是Log.e()。運行之後我們得到了下面的log,證明我們的是沒有問題的。

09-03 11:16:41.426 13194-13194/com.liteng.mytest E/BitmapFactoryTest: 圖片的MIME類型 = image/jpeg
09-03 11:16:41.426 13194-13194/com.liteng.mytest E/BitmapFactoryTest: 圖片的高 = 683
09-03 11:16:41.426 13194-13194/com.liteng.mytest E/BitmapFactoryTest: 圖片的寬 = 1024

2.計算Bitmap合適的尺寸

我們獲取了原圖的尺寸,我們想把它顯示到一個寬高分別為200,100的ImageView上,那我們怎麼計算呢?

這裡我們封裝一個方法:

private int computeInSampleSize(BitmapFactory.Options options, int targetW, int targetH) {
    int width = options.outWidth;
    int height = options.outHeight;

    int inSampleSize = 1;
    //判斷一下原圖的寬高與我們的目標寬高大小,如果原圖的長或者寬大於目標寬高才計算
    if (width > targetW || height > targetH) {
        int halfH = height / 2;
        int halfW = width / 2;
        //inSimpleSize的必須是2的指數次冪,所以我們取最可能的inSampleSize的最大值
        while ((halfH / inSampleSize) > targetH && (halfW / inSampleSize) > targetW) {
            inSampleSize *= 2;
        }
    }
    return  inSampleSize;
}

3.設置inSampleSize,壓縮Bitmap,並顯示在ImageView上

BitmapFactory.Options options = new BitmapFactory.Options();
//這裡我獲取圖片的尺寸和類型信息
options.inJustDecodeBounds = true;
//我們這裡獲取到的Bitmap是空的,圖片沒有真正的加載到內存,只有尺寸和類型信息
Bitmap bitmap = BitmapFactory.decodeFile(getBeautyPath(), options);
//為了確定為空,我們做一下判斷
if (bitmap == null) {
    loge("圖片的MIME類型 = " + options.outMimeType);
    loge("圖片的高 = " + options.outHeight);
    loge("圖片的寬 = " + options.outWidth);
}

//獲取計算到的inSampleSize
options.inSampleSize = computeInSampleSize(options,200,100);
//我們不再只獲取圖片的尺寸和類型了,下一步我們需要加載Bitmap了
options.inJustDecodeBounds = false;
Bitmap targetBitmap = BitmapFactory.decodeFile(getBeautyPath(),options);
ivBeauty.setImageBitmap(targetBitmap);

那麼經過我們壓縮過的圖片是什麼樣的呢?看圖

\

那麼我們在看一下log呢?

09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: 圖片的MIME類型 = image/jpeg
09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: 圖片的高 = 683
09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: 圖片的寬 = 1024
09-03 12:03:19.957 8642-8642/com.liteng.mytest E/BitmapFactoryTest: inSampleSize = 4

我們計算出來的inSampleSize為4,之前我們說過,in SampleSize只能是2的指數次冪,我們這裡只能盡可能接近我們需要值,而不能完全精確。

上面我們說到Bitmap.Config 這個枚舉,它的代碼如下(去掉了注釋和空行):

public enum Config {
    ALPHA_8     (1),
    RGB_565     (3),
    @Deprecated
    ARGB_4444   (4),
    ARGB_8888   (5);
    final int nativeInt;
    private static Config sConfigs[] = {
        null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888
    };
    Config(int ni) {
        this.nativeInt = ni;
    }
    static Config nativeToConfig(int ni) {
        return sConfigs[ni];
    }
}

我們都知道Bitmap占有內存為:
Bitmap占有的內存=圖片像素高度×圖片像素寬度×每個像素占有的內存=像素總數×每個像素占有的內存
我們在上面代碼中看到的 ALPHA_8,RGB_565,ARGB_4444 ,ARGB_8888就是Bitmap的四種像素形式,它們每個像素占用的字節數分別為4、2、2、1;即
ALPHA8:RGB565:ARGB4444:ARGB8888=4:2:2:1
其中ARGB4444在API13被廢棄了。

下面是整個Activity的代碼,感興趣的可以再封裝一下,做一個工具類:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "BitmapFactoryTest";

    private ImageView ivBeauty;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ivBeauty = (ImageView) this.findViewById(R.id.ivBeauty);

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_4444;
        //這裡我獲取圖片的尺寸和類型信息
        options.inJustDecodeBounds = true;
        //我們這裡獲取到的Bitmap是空的,圖片沒有真正的加載到內存,只有尺寸和類型信息
        Bitmap bitmap = BitmapFactory.decodeFile(getBeautyPath(), options);
        //為了確定為空,我們做一下判斷
        if (bitmap == null) {
            loge("圖片的MIME類型 = " + options.outMimeType);
            loge("圖片的高 = " + options.outHeight);
            loge("圖片的寬 = " + options.outWidth);
        }

        //獲取計算到的inSampleSize
        options.inSampleSize = computeInSampleSize(options,200,100);
        loge("inSampleSize = " + options.inSampleSize);
        //我們不再只獲取圖片的尺寸和類型了,下一步我們需要加載Bitmap了
        options.inJustDecodeBounds = false;

        Bitmap targetBitmap = BitmapFactory.decodeFile(getBeautyPath(),options);
        ivBeauty.setImageBitmap(targetBitmap);
    }

    private int computeInSampleSize(BitmapFactory.Options options, int targetW, int targetH) {
        int width = options.outWidth;
        int height = options.outHeight;

        int inSampleSize = 1;
        //判斷一下原圖的寬高與我們的目標寬高大小,如果原圖的長或者寬大於目標寬高才計算
        if (width > targetW || height > targetH) {
            int halfH = height / 2;
            int halfW = width / 2;
            //inSimpleSize的必須是2的指數次冪,所以我們取最可能的inSampleSize的最大值
            while ((halfH / inSampleSize) > targetH && (halfW / inSampleSize) > targetW) {
                inSampleSize *= 2;
            }
        }
        return  inSampleSize;
    }


    private String getBeautyPath() {
        String path = Environment.getExternalStorageDirectory() + "/Download/beauty.jpg";
        return path;
    }


    private void loge(String msg) {
        Log.e(TAG, msg);
    }
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved