【Google官方教程】第一課:高效地載入大Bitmap(點陣圖)

yangxi_001發表於2013-11-26

在Google最新的文件中,提供了一系列含金量相當高的教程。因為種種原因而鮮為人知,真是可惜!Ryan將會細心整理,將之翻譯成中文,希望對開發者有所幫助。

        本系列是Google關於展示大Bitmap(點陣圖)的官方演示,可以有效的解決記憶體限制,更加有效的載入並顯示圖片,同時避免讓人頭疼的OOM(Out Of Memory)。

-------------------------------------------------------------------------------------

譯文:

         影象可以有各種各樣的形狀和大小。在很多情況下,它們往往會比典型的應用UI介面所需要的更大。例如,系統的Gallery程式展示使用Android裝置的攝像頭拍攝的照片的解析度往往要遠高於裝置的螢幕密度。 

        考慮到你所使用的記憶體有限,理想的情況是你只會想載入一個解析度相對較低的圖片到記憶體中來。低解析度版本的圖片與相應UI元件的尺寸應該是相匹配的。一張高解析度的圖片並不能帶給你任何可見的好處,卻要佔據著寶貴的記憶體,以及間接導致由於動態縮放引起額外的效能開銷。 

        這節課將向你演示如何解碼大圖片,通過載入較小的圖片樣本以避免超出應用的記憶體限制。


讀取Bitmap(點陣圖)的尺寸和型別

        BitmapFactory提供了幾種解碼方式(decodeByteArray(), decodeFile(), decodeResource()等等),以便從多種資源中建立一個Bitmap(點陣圖)物件。可以根據你的圖片資料來源選擇最合適的解碼方式。這些方法檢視為構造Bitmap物件分配記憶體,因此很容易導致OutOfMemory(OOM)異常。每一種解碼方式都有額外的特徵,你可以通過BitmapFactory.Options類指定解碼方法。在解碼圖片的時候設定inJustDecodeBounds屬性為true,可以避免記憶體分配,返回的bitmap物件為null卻可以設定outWidth, outHeight和outMimeType。這項技術允許你在建立Bitmap(並分配記憶體)之前讀取圖片的尺寸和型別。

1 BitmapFactory.Options options = new BitmapFactory.Options();
2 options.inJustDecodeBounds = true;
3 BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
4 int imageHeight = options.outHeight;
5 int imageWidth = options.outWidth;
6 String imageType = options.outMimeType;
        為了避免java.lang.OutOfMemeory異常,在解碼圖片之前就要檢查圖片的尺寸,除非你十分確信圖片資源的尺寸是可預見的並且有著充裕的可用記憶體。 

將縮小版的圖片載入到記憶體中 

        現在圖片的尺寸已經知道了,這些資訊可以用來決定是將一個完整尺寸的圖片載入到記憶體中,還是應該用一個圖片的子樣本來取代它。這裡有一些可供考慮的因素: 
  • 估計載入全尺寸的圖片所要消耗的記憶體
  • 在考慮應用中其他記憶體需求的情況下,你願意給載入這個圖片分配的記憶體空間。
  • 準備載入該影象的目標ImageView或者UI元件的尺寸
  • 當前裝置的螢幕的尺寸和密度

        例如,如果最終只是要在ImageView中顯示一張128*96px大小的縮圖,直接載入1024*768px的圖片是非常不值得的。

        為了告訴解碼器如何對影象進行取樣,載入更小版本的圖片,需要在BitmapFactory.Options物件中將inSampleSize設定為true。例如,一張解析度為2048*1536px的影象使用inSampleSize值為4的設定來解碼,產生的Bitmap大小約為512*384px。相較於完整圖片佔用12M的記憶體,這種方式只需0.75M記憶體(假設Bitmap配置為ARGB_8888)。這裡有一個方法用來計算基於目標高寬的sample size的值:

01 public static int calculateInSampleSize(
02             BitmapFactory.Options options, int reqWidth, int reqHeight) {
03     // Raw height and width of image
04     final int height = options.outHeight;
05     final int width = options.outWidth;
06     int inSampleSize = 1;
07  
08     if (height > reqHeight || width > reqWidth) {
09         if (width > height) {
10             inSampleSize = Math.round((float)height / (float)reqHeight);
11         else {
12             inSampleSize = Math.round((float)width / (float)reqWidth);
13         }
14     }
15     return inSampleSize;
16 }

        提示:使用2的次冪來設定inSampleSize值可以使解碼器執行地更加迅速、更加高效。但是,如果你想在記憶體或者硬碟上快取一個調整過大小的圖片,通常還是解碼到合適的圖片尺寸更加節省空間。

        要使用這個方法,首先要使用inJustDecodeBoundstrue來解碼尺寸資訊,將options傳遞過去使用新的inSampleSize值再次解碼並且要將inJustDecodeBounds值設定為false

01 public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
02         int reqWidth, int reqHeight) {
03  
04     // First decode with inJustDecodeBounds=true to check dimensions
05     final BitmapFactory.Options options = new BitmapFactory.Options();
06     options.inJustDecodeBounds = true;
07     BitmapFactory.decodeResource(res, resId, options);
08  
09     // Calculate inSampleSize
10     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
11  
12     // Decode bitmap with inSampleSize set
13     options.inJustDecodeBounds = false;
14     return BitmapFactory.decodeResource(res, resId, options);
15 }
        這個方法使得載入任意大小的Bitmap到展示100*100px縮圖的ImageView中更加簡單,如下程式碼所示: 
1 mImageView.setImageBitmap(
2     decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100100));
        你可以根據需要,按照類似的解碼過程,採用適當的BitmapFactory.decode*方法從其他資源中解碼Bitmap。 

相關文章