Android開發之有效加載大尺寸位圖

前言

圖片有不同的形狀與大小。在大多數情況下它們的實際大小都比需要呈現出來的要大很多。例如,手機拍出來的照片的分辨率通常都比你的設備屏幕分辨率要高很多。

考慮到程序是在有限的內存下工作,理想情況是你只需要在內存中加載一個低分辨率的版本即可。這個低分辨率的版本應該是與你的UI大小所匹配的,這樣才便於顯示。一個高分辨率的圖片不會提供任何可見的好處,卻會佔用寶貴的(precious)的內存資源,並且會在快速滑動圖片時導致(incurs)附加的效率問題。

我們應學會如何通過加載一個縮小版本的圖片到內存中去decoding大的bitmaps,從而避免超出程序的內存限制。

讀取位圖的尺寸與類型

BitmapFactory 類提供了一些decode的方法用來從不同的資源中創建一個Bitmap. 根據你的圖片數據源來選擇合適的 decode 方法. 那些方法在構造位圖的時候會嘗試分配內存,因此會容易導致OutOfMemory的異常。每一種decode方法都提供了通過BitmapFactory.Options 來設置一些附加的標記來指定decode的選項。設置 inJustDecodeBounds 屬性爲true可以在decoding的時候避免內存的分配,它會返回一個null的bitmap,但是 outWidth, outHeight 與 outMimeType 還是可以獲取。這個技術可以允許你在構造bitmap之前優先讀圖片的尺寸與類型。

    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 的異常,我們需要在真正decode圖片之前檢查它的尺寸,除非你確定這個數據源提供了準確無誤的圖片且不會導致佔用過多的內存。

加載一個按比例縮小的版本到內存中

通過上面的步驟我們已經知道了圖片的尺寸,那些數據可以用來決定是應該加載整個圖片到內存中還是加載一個縮小的版本。有下面一些因素需要考慮:

  • 評估加載完整圖片所需要耗費的內存。
  • 程序在加載這張圖片時會涉及到其他內存需求
  • 呈現這張圖片的組件的尺寸大小
  • 屏幕大小與當前設備的屏幕密度

例如,如果把一個原圖是1024*768 pixel的圖片顯示到ImageView爲128*96 pixel的縮略圖就沒有必要把整張圖片都加載到內存中。

爲了告訴decoder去加載一個低版本的圖片到內存,需要在你的BitmapFactory.Options 中設置 inSampleSizetrue 。For example, 一個分辨率爲2048x1536 的圖片,如果設置 inSampleSize 爲4,那麼會產出一個大概爲512x384的bitmap。加載這張小的圖片僅僅使用大概0.75MB,如果是加載全圖那麼大概要花費12MB(前提都是bitmap的配置是 ARGB_8888). 下面有一段根據目標圖片大小來計算Sample圖片大小的Sample Code:

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

注意:設置inSampleSize爲2的冪是因爲decoder最終還是會對非2的冪的數進行向下處理,獲取到最靠近2的冪的數。詳情參考inSampleSize的文檔。

爲了使用這個方法,首先需要設置 inJustDecodeBoundstrue, 把options的值傳遞過來,然後使用 inSampleSize 的值並設置 inJustDecodeBounds 爲 false 來重新Decode一遍。

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

使用上面這個方法可以簡單的加載一個任意大小的圖片並顯示爲100*100 pixel的縮略圖形式。像下面演示的一樣:

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

你可以通過替換合適的BitmapFactory.decode* 方法來寫一個類似的方法從其他的數據源進行decode bitmap。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章