Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android官方開發文檔Training系列課程中文版:高效顯示位圖之加載大位圖

Android官方開發文檔Training系列課程中文版:高效顯示位圖之加載大位圖

編輯:關於Android編程

引言

學習如何使用一種常規的手段來處理及加載Bitmap對象,這種方式除了使用戶界面是可響應的之外,還會避免超出內存的限制。如果你不小心點的話,位圖會迅速的將那些可憐的內存消耗殆盡,並會導致程序崩潰,因為這會產生一種可怕的異常:

java.lang.OutofMemoryError: bitmap size exceeds VM budget.

這裡列舉出了一些原因來說明為什麼加載位圖對於Android程序來說是非常棘手的:

移動設備通常含有有限的資源。Android設備對於單個程序只有少量的16MB可用內存。虛擬機兼容性(Virtual Machine Compatibility)針對於各種的屏幕尺寸和密度給出了最低限度的程序內存要求。程序應該在極小的內存空間下充分利用內存空間。無論如何要記住一點,很多設備配備了更高的限制。 位圖通常會消耗掉不少內存,尤其是豐富的圖片,就像照片這樣的。舉個例子,Galaxy Nexus上的相機拍的照片會達到2592x1936個像素(五百萬像素)。如果位圖配置使用的是ARGB_8888(這在Android 2.3以前是默認的),那麼加載這張照片到內存中就需要花費掉19MB的內存(2592*1936*4個字節),這會立即耗盡某些設備上的所有內存。 Android的APP界面有時會很頻繁的請求一些圖片來加載。有些組件比如ListView, GridView及ViewPager,它們有個共同的特性就是需要同時在屏幕上加載多個位圖並會在屏幕之外的地方加載以便在手指滑動的時候顯示出來。

有效加載大圖

圖片會有各種形狀和大小。在很多情況下它們會比用戶界面上所要求的尺寸要大。舉個例子,系統的相冊應用所展示的用相機拍攝的照片的分辨率通常要比屏幕的密度要高。

鑒於在有限的內存中工作,理想上只用加載低分辨率的版本就可以。低分辨率的版本應該匹配到展示這張圖片的控件大小。圖片的更高分辨率不會在視覺上有更佳的效果,但是這仍然會消耗寶貴的內存空間,由於額外的動態擴展,這會招致額外的性能開銷。

這節課會討論將大位圖進行二次采樣並將采樣後的小版本加載到內存中的過程。這個過程並不會超出應用的內存限制。

讀取位圖的尺寸及類型

類BitmapFactory提供了若干個解碼方法(decodeByteArray(), decodeFile(), decodeResource(), etc.)根據不同的資源來創建位圖Bitmap。選擇更加適合的解碼方法取決於圖片的數據資源。這些方法會在構造位圖時嘗試向內存申請空間,所以會輕易的造成OutOfMemory異常。每個解碼方法都有一個附屬特征,這個特征可以使你通過BitmapFactory.Options類來指定解碼選項。設置inJustDecodeBounds屬性為true可以避免在解碼時向內存申請空間,這會返回一個空的位圖,但是outWidth、outHeight和outMimeType這些設置除外。這項技術可以使你在構造位圖(申請內存)之前提前讀取圖像數據的尺寸及類型。

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異常,需要在解碼圖片之前檢查圖片的尺寸,除非你對這些圖像數據的尺寸絕對的信任,並且該尺寸對可用內存非常適用。

加載等比縮小的版本到內存

那麼現在圖片的尺寸是知道了,這尺寸可以被用來決定:是否全尺寸的圖像應該被加載到內存中還是應該有個二次采樣的版本加載到內存中。這裡有一些因素需要考慮:

往內存中加載全尺寸的圖像應該估算要使用的內存大小。 要加載的圖片所需要的內存數量需要給應用預留一定的內存空間,不要消耗完全。 ImageView或者UI組件的尺寸是圖像將要加載的尺寸。 當前設備的屏幕尺寸與密度。

舉個例子,加載一個1024*768像素的圖片到內存中是沒有價值的,如果這個圖片最終被顯示為一個128x96像素的縮略圖的話。

為了告訴解碼器需要進行二次采樣,以便加載一個小版本的圖像到內存中,需要設置BitmapFactory.Options對象的inSampleSize屬性為true。舉個例子,一張圖片的分辨率為2048x1536,需要通過inSampleSize解碼為4分之一大小的位圖,大概是512x384。加載這樣的圖像只需要花費0.75MB內存,而全尺寸的圖像則需要花費12MB的內存(假設位圖的配置為ARGB_8888)。這裡有一個方法可以來計算一個樣本容量值,這個值是2的冪次方值並基於原圖像的高度值與寬度值進行計算。

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: 最終計算後的值是一個2的冪次方值是因為解碼器需要通過捨入來獲得一個最終值,這個值與2的冪次方最為接近,依據inSampleSize文檔。

為了使用這個方法,第一步需要將inJustDecodeBounds設置為true,然後將options交給BitmapFactory使用,然後再次使用一個新的inSampleSize和inJustDecodeBounds設置為false來再次使用:

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

這個方法可以很輕易的加載任何大尺寸的位圖給ImageView,這個ImageView展示了一個100*100像素的縮略圖,就像下面的代碼所展示的這樣:

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

你可以遵循類似的過程來對其它資源進行解碼,如果需要的話,可以替代使用合適的BitmapFactory.decode*方法。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved