注意:許多程式庫都會採用最佳做法載入圖片。您可以在應用程式中使用這些程式庫,以最適合的方式載入圖片。建議您使用 Glide 程式庫,載入/顯示圖片最快也最順暢。其他常用的圖片載入程式庫還包括 Square 的 Picasso、Instacart 的 Coil,以及 Facebook 的 Fresco。這些程式庫可簡化與 Android 點陣圖和其他類型圖片相關的大多數複雜工作。
圖片有各種形狀和大小,所占用的空間多半大於一般應用程式使用者介面 (UI)。舉例來說,系統 Gallery 應用程式會顯示使用 Android 裝置相機拍攝的相片,這些相片的解析度通常遠高於裝置的畫面密度。
由於可用的記憶體有限,建議您僅在記憶體中載入解析度較低的版本。此版本的大小應與顯示該版本的 UI 元件相符。解析度較高的圖片不僅沒有顯著優勢,還會占用寶貴的記憶體,且會在執行其他縮放操作時產生額外的效能負擔。
本課程會逐步說明如何在記憶體中載入較小的向下取樣版本,讓您既可以解碼大型點陣圖,又不會超過個別應用程式的記憶體限制。
讀取點陣圖尺寸和類型
BitmapFactory
類別提供可從多種來源建立 Bitmap
的數種解碼方法 (decodeByteArray()
、decodeFile()
、decodeResource()
等)。請根據圖片資料來源選擇最合適的解碼方法。這些方法會試圖為已建構的點陣圖分配記憶體,因此很容易引發 OutOfMemory
例外狀況。每種解碼方法都有額外的簽名,可讓您透過 BitmapFactory.Options
類別指定解碼選項。在解碼時將 inJustDecodeBounds
屬性設為 true
,可省去記憶體配置程序,也就是能為點陣圖物件傳回 null
,但設定 outWidth
、outHeight
和 outMimeType
。此技術可讓您在點陣圖建構 (和記憶體配置) 程序之前,讀取圖片資料的尺寸和類型。
Kotlin
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } BitmapFactory.decodeResource(resources, R.id.myimage, options) val imageHeight: Int = options.outHeight val imageWidth: Int = options.outWidth val imageType: String = options.outMimeType
Java
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 x 768 像素的圖片最終會在 ImageView
中顯示為 128 x 96 像素的縮圖,那就不值得將其載入記憶體中。
如要讓解碼器對圖片進行向下取樣,請將 BitmapFactory.Options
物件的 inSampleSize
設為 true
,在記憶體中載入較小的版本。舉例來說,如果圖片的解析度是 2048 x 1536,且解碼時的 inSampleSize
為 4,就會產生大約 512 x 384 的點陣圖。將此載入記憶體中會用掉 0.75 MB,而非完整圖片所需的 12 MB (假設點陣圖設定為 ARGB_8888
)。以下是根據目標寬度和高度計算範本大小值的方法,該值為二的次方:
Kotlin
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { // Raw height and width of image val (height: Int, width: Int) = options.run { outHeight to outWidth } var inSampleSize = 1 if (height > reqHeight || width > reqWidth) { val halfHeight: Int = height / 2 val halfWidth: Int = 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 }
Java
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
說明文件所述,解碼器使用最終值時會四捨五入至最接近的二次方,因此計算的是二次方值。
如要使用這個方法,請先將 inJustDecodeBounds
設為 true
來進行解碼,然後傳遞選項
,然後使用新的 inSampleSize
值再次解碼,並將 inJustDecodeBounds
設為 false
:
Kotlin
fun decodeSampledBitmapFromResource( res: Resources, resId: Int, reqWidth: Int, reqHeight: Int ): Bitmap { // First decode with inJustDecodeBounds=true to check dimensions return BitmapFactory.Options().run { inJustDecodeBounds = true BitmapFactory.decodeResource(res, resId, this) // Calculate inSampleSize inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight) // Decode bitmap with inSampleSize set inJustDecodeBounds = false BitmapFactory.decodeResource(res, resId, this) } }
Java
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 x 100 像素縮圖的 ImageView
中,如下列範例程式碼所示:
Kotlin
imageView.setImageBitmap( decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100) )
Java
imageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
您可以按照類似的程序解碼其他來源的點陣圖,只要視需要替換適當的 BitmapFactory.decode*
方法即可大功告成。