Android怎么高效加載大圖

2018-08-02 17:35 更新

編寫:kesenhoo - 原文:http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

圖片有不同的形狀與大小。在大多數(shù)情況下它們的實際大小都比需要呈現(xiàn)的尺寸大很多。例如,系統(tǒng)的圖庫應(yīng)用會顯示那些我們使用相機拍攝的照片,但是那些圖片的分辨率通常都比設(shè)備屏幕的分辨率要高很多。

考慮到應(yīng)用是在有限的內(nèi)存下工作的,理想情況是我們只需要在內(nèi)存中加載一個低分辨率的照片即可。為了更便于顯示,這個低分辨率的照片應(yīng)該是與其對應(yīng)的UI控件大小相匹配的。加載一個超過屏幕分辨率的高分辨率照片不僅沒有任何顯而易見的好處,還會占用寶貴的內(nèi)存資源,另外在快速滑動圖片時容易產(chǎn)生額外的效率問題。

這一課會介紹如何通過加載一個縮小版本的圖片,從而避免超出程序的內(nèi)存限制。

讀取位圖的尺寸與類型(Read Bitmap Dimensions and Type)

BitmapFactory提供了一些解碼(decode)的方法(decodeByteArray()decodeFile()decodeResource()等),用來從不同的資源中創(chuàng)建一個Bitmap。 我們應(yīng)該根據(jù)圖片的數(shù)據(jù)源來選擇合適的解碼方法。 這些方法在構(gòu)造位圖的時候會嘗試分配內(nèi)存,因此會容易導(dǎo)致OutOfMemory的異常。每一種解碼方法都可以通過BitmapFactory.Options設(shè)置一些附加的標(biāo)記,以此來指定解碼選項。設(shè)置 inJustDecodeBounds 屬性為true可以在解碼的時候避免內(nèi)存的分配,它會返回一個null的Bitmap,但是可以獲取到 outWidth, outHeight 與 outMimeType。該技術(shù)可以允許你在構(gòu)造Bitmap之前優(yōu)先讀圖片的尺寸與類型。

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;

為了避免java.lang.OutOfMemory 的異常,我們需要在真正解析圖片之前檢查它的尺寸(除非你能確定這個數(shù)據(jù)源提供了準確無誤的圖片且不會導(dǎo)致占用過多的內(nèi)存)。

加載一個按比例縮小的版本到內(nèi)存中(Load a Scaled Down Version into Memory)

通過上面的步驟我們已經(jīng)獲取到了圖片的尺寸,這些數(shù)據(jù)可以用來幫助我們決定應(yīng)該加載整個圖片到內(nèi)存中還是加載一個縮小的版本。有下面一些因素需要考慮:

  • 評估加載完整圖片所需要耗費的內(nèi)存。
  • 程序在加載這張圖片時可能涉及到的其他內(nèi)存需求。
  • 呈現(xiàn)這張圖片的控件的尺寸大小。
  • 屏幕大小與當(dāng)前設(shè)備的屏幕密度。

例如,如果把一個大小為1024x768像素的圖片顯示到大小為128x96像素的ImageView上嗎,就沒有必要把整張原圖都加載到內(nèi)存中。

為了告訴解碼器去加載一個縮小版本的圖片到內(nèi)存中,需要在BitmapFactory.Options 中設(shè)置 inSampleSize 的值。例如, 一個分辨率為2048x1536的圖片,如果設(shè)置 inSampleSize 為4,那么會產(chǎn)出一個大約512x384大小的Bitmap。加載這張縮小的圖片僅僅使用大概0.75MB的內(nèi)存,如果是加載完整尺寸的圖片,那么大概需要花費12MB(前提都是Bitmap的配置是 ARGB_8888)。下面有一段根據(jù)目標(biāo)圖片大小來計算Sample圖片大小的代碼示例:

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;
}

Note: 設(shè)置inSampleSize為2的冪是因為解碼器最終還是會對非2的冪的數(shù)進行向下處理,獲取到最靠近2的冪的數(shù)。詳情參考inSampleSize的文檔。

為了使用該方法,首先需要設(shè)置 inJustDecodeBounds 為 true, 把options的值傳遞過來,然后設(shè)置 inSampleSize 的值并設(shè)置 inJustDecodeBounds 為 false,之后重新調(diào)用相關(guān)的解碼方法。

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();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

使用上面這個方法可以簡單地加載一張任意大小的圖片。如下面的代碼樣例顯示了一個接近 100x100像素的縮略圖:

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

我們可以通過替換合適的BitmapFactory.decode* 方法來實現(xiàn)一個類似的方法,從其他的數(shù)據(jù)源解析Bitmap。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號