큰 비트맵을 효율적으로 로드
컬렉션을 사용해 정리하기
내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.
참고: 이미지 로드의 권장사항을 따르는 라이브러리는 여러 가지가 있습니다. 앱에서 이러한 라이브러리를 사용하여 가장 최적화된 방식으로 이미지를 로드할 수 있습니다. Android는 Glide 라이브러리를 추천하며 이 라이브러리는 최대한 빨리 그리고 부드럽게 이미지를 로드하고 표시합니다.
인기 있는 다른 이미지 로딩 라이브러리로는 Square의 Picasso, Instacart의 Coil, Facebook의 Fresco 등이 있습니다. 이러한 라이브러리는 Android에서 비트맵 및 기타 이미지 유형과 관련된 복잡한 작업의 대부분을 간소화합니다.
이미지의 모양과 크기는 다양합니다. 많은 경우 이미지는 일반적인 애플리케이션 사용자 인터페이스(UI)에 비해 크기가 큽니다. 예를 들어, 시스템 갤러리 애플리케이션은 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 구성요소의 크기
- 현재 기기의 화면 크기 및 밀도
예를 들어, 1024x768픽셀 이미지가 결국에는 ImageView
에 128x96픽셀의 썸네일 이미지로 표시된다면 1024x768픽셀 이미지를 메모리에 로드하는 것이 의미가 없습니다.
이미지를 서브 샘플링하여 더 작은 버전을 메모리에 로드하도록 디코더에 지시하려면 BitmapFactory.Options
객체에서 inSampleSize
를 true
로 설정하면 됩니다. 예를 들어, 해상도가 2048x1536이고 inSampleSize
가 4로 디코딩된 이미지는 약 512x384의 비트맵을 생성합니다. 이 비트맵을 메모리에 로드하면 전체 이미지에 12MB 대신 0.75MB가 사용됩니다(비트맵 구성은 ARGB_8888
이라고 가정함). 다음은 타겟 너비와 높이를 기준으로 2의 거듭제곱인 샘플 크기 값을 계산하는 메서드입니다.
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
문서에 따라 디코더가 2의 거듭제곱에 가장 가까운 값으로 내림하여 최종 값을 사용하기 때문에 2의 거듭제곱 값이 계산됩니다.
이 메서드를 사용하려면 먼저 inJustDecodeBounds
를 true
로 설정한 상태에서 디코딩한 다음 옵션을 전달하고 새 inSampleSize
값과 false
로 설정한 inJustDecodeBounds
를 사용하여 다시 디코딩합니다.
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);
}
이 메서드를 사용하면 다음 코드 예에서와 같이 100x100픽셀의 썸네일을 표시하는 ImageView
에 임의의 큰 비트맵을 쉽게 로드할 수 있습니다.
Kotlin
imageView.setImageBitmap(
decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100)
)
Java
imageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
필요에 따라 적절한 BitmapFactory.decode*
메서드를 대체하여 비슷한 절차를 따라 다른 소스의 비트맵을 디코딩할 수 있습니다.
이 페이지에 나와 있는 콘텐츠와 코드 샘플에는 콘텐츠 라이선스에서 설명하는 라이선스가 적용됩니다. 자바 및 OpenJDK는 Oracle 및 Oracle 계열사의 상표 또는 등록 상표입니다.
최종 업데이트: 2025-07-27(UTC)
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["필요한 정보가 없음","missingTheInformationINeed","thumb-down"],["너무 복잡함/단계 수가 너무 많음","tooComplicatedTooManySteps","thumb-down"],["오래됨","outOfDate","thumb-down"],["번역 문제","translationIssue","thumb-down"],["샘플/코드 문제","samplesCodeIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2025-07-27(UTC)"],[],[],null,["# Loading Large Bitmaps Efficiently\n\n**Note:** There are several libraries that follow\nbest practices for loading images. You can use these libraries in your app to\nload images in the most optimized manner. We recommend the\n[Glide](https://github.com/bumptech/glide)\nlibrary, which loads and displays images as quickly and smoothly as possible.\nOther popular image loading libraries include [Picasso](http://square.github.io/picasso/) from Square, [Coil](https://github.com/coil-kt/coil) from Instacart, and\n[Fresco](https://github.com/facebook/fresco)\nfrom Facebook. These libraries simplify most of the complex tasks associated\nwith bitmaps and other types of images on Android.\n\nImages come in all shapes and sizes. In many cases they are larger than required for a typical\napplication user interface (UI). For example, the system Gallery application displays photos taken\nusing your Android devices's camera which are typically much higher resolution than the screen\ndensity of your device.\n\nGiven that you are working with limited memory, ideally you only want to load a lower resolution\nversion in memory. The lower resolution version should match the size of the UI component that\ndisplays it. An image with a higher resolution does not provide any visible benefit, but still takes\nup precious memory and incurs additional performance overhead due to additional on the fly\nscaling.\n\nThis lesson walks you through decoding large bitmaps without exceeding the per application\nmemory limit by loading a smaller subsampled version in memory.\n\nRead Bitmap Dimensions and Type\n-------------------------------\n\nThe [BitmapFactory](/reference/android/graphics/BitmapFactory) class provides several decoding methods ([decodeByteArray()](/reference/android/graphics/BitmapFactory#decodeByteArray(byte[], int, int, android.graphics.BitmapFactory.Options)), [decodeFile()](/reference/android/graphics/BitmapFactory#decodeFile(java.lang.String, android.graphics.BitmapFactory.Options)), [decodeResource()](/reference/android/graphics/BitmapFactory#decodeResource(android.content.res.Resources, int, android.graphics.BitmapFactory.Options)), etc.) for creating a [Bitmap](/reference/android/graphics/Bitmap) from various sources. Choose\nthe most appropriate decode method based on your image data source. These methods attempt to\nallocate memory for the constructed bitmap and therefore can easily result in an `OutOfMemory`\nexception. Each type of decode method has additional signatures that let you specify decoding\noptions via the [BitmapFactory.Options](/reference/android/graphics/BitmapFactory.Options) class. Setting the [inJustDecodeBounds](/reference/android/graphics/BitmapFactory.Options#inJustDecodeBounds) property to `true` while decoding\navoids memory allocation, returning `null` for the bitmap object but setting [outWidth](/reference/android/graphics/BitmapFactory.Options#outWidth), [outHeight](/reference/android/graphics/BitmapFactory.Options#outHeight) and [outMimeType](/reference/android/graphics/BitmapFactory.Options#outMimeType). This technique allows you to read the\ndimensions and type of the image data prior to construction (and memory allocation) of the\nbitmap. \n\n### Kotlin\n\n```kotlin\nval options = BitmapFactory.Options().apply {\n inJustDecodeBounds = true\n}\nBitmapFactory.decodeResource(resources, R.id.myimage, options)\nval imageHeight: Int = options.outHeight\nval imageWidth: Int = options.outWidth\nval imageType: String = options.outMimeType\n```\n\n### Java\n\n```java\nBitmapFactory.Options options = new BitmapFactory.Options();\noptions.inJustDecodeBounds = true;\nBitmapFactory.decodeResource(getResources(), R.id.myimage, options);\nint imageHeight = options.outHeight;\nint imageWidth = options.outWidth;\nString imageType = options.outMimeType;\n```\n\nTo avoid `java.lang.OutOfMemory` exceptions, check the dimensions of a bitmap before\ndecoding it, unless you absolutely trust the source to provide you with predictably sized image data\nthat comfortably fits within the available memory.\n\nLoad a Scaled Down Version into Memory\n--------------------------------------\n\nNow that the image dimensions are known, they can be used to decide if the full image should be\nloaded into memory or if a subsampled version should be loaded instead. Here are some factors to\nconsider:\n\n- Estimated memory usage of loading the full image in memory.\n- Amount of memory you are willing to commit to loading this image given any other memory requirements of your application.\n- Dimensions of the target [ImageView](/reference/android/widget/ImageView) or UI component that the image is to be loaded into.\n- Screen size and density of the current device.\n\nFor example, it's not worth loading a 1024x768 pixel image into memory if it will eventually be\ndisplayed in a 128x96 pixel thumbnail in an [ImageView](/reference/android/widget/ImageView).\n\nTo tell the decoder to subsample the image, loading a smaller version into memory, set [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) to `true` in your [BitmapFactory.Options](/reference/android/graphics/BitmapFactory.Options) object. For example, an image with resolution 2048x1536 that\nis decoded with an [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) of 4 produces a\nbitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full\nimage (assuming a bitmap configuration of [ARGB_8888](/reference/android/graphics/Bitmap.Config)). Here's\na method to calculate a sample size value that is a power of two based on a target width and\nheight: \n\n### Kotlin\n\n```kotlin\nfun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {\n // Raw height and width of image\n val (height: Int, width: Int) = options.run { outHeight to outWidth }\n var inSampleSize = 1\n\n if (height \u003e reqHeight || width \u003e reqWidth) {\n\n val halfHeight: Int = height / 2\n val halfWidth: Int = width / 2\n\n // Calculate the largest inSampleSize value that is a power of 2 and keeps both\n // height and width larger than the requested height and width.\n while (halfHeight / inSampleSize \u003e= reqHeight && halfWidth / inSampleSize \u003e= reqWidth) {\n inSampleSize *= 2\n }\n }\n\n return inSampleSize\n}\n```\n\n### Java\n\n```java\npublic static int calculateInSampleSize(\n BitmapFactory.Options options, int reqWidth, int reqHeight) {\n // Raw height and width of image\n final int height = options.outHeight;\n final int width = options.outWidth;\n int inSampleSize = 1;\n\n if (height \u003e reqHeight || width \u003e reqWidth) {\n\n final int halfHeight = height / 2;\n final int halfWidth = width / 2;\n\n // Calculate the largest inSampleSize value that is a power of 2 and keeps both\n // height and width larger than the requested height and width.\n while ((halfHeight / inSampleSize) \u003e= reqHeight\n && (halfWidth / inSampleSize) \u003e= reqWidth) {\n inSampleSize *= 2;\n }\n }\n\n return inSampleSize;\n}\n```\n\n**Note:** A power of two value is calculated because the decoder uses\na final value by rounding down to the nearest power of two, as per the [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) documentation.\n\nTo use this method, first decode with [inJustDecodeBounds](/reference/android/graphics/BitmapFactory.Options#inJustDecodeBounds) set to `true`, pass the options\nthrough and then decode again using the new [inSampleSize](/reference/android/graphics/BitmapFactory.Options#inSampleSize) value and [inJustDecodeBounds](/reference/android/graphics/BitmapFactory.Options#inJustDecodeBounds) set to `false`: \n\n### Kotlin\n\n```kotlin\nfun decodeSampledBitmapFromResource(\n res: Resources,\n resId: Int,\n reqWidth: Int,\n reqHeight: Int\n): Bitmap {\n // First decode with inJustDecodeBounds=true to check dimensions\n return BitmapFactory.Options().run {\n inJustDecodeBounds = true\n BitmapFactory.decodeResource(res, resId, this)\n\n // Calculate inSampleSize\n inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)\n\n // Decode bitmap with inSampleSize set\n inJustDecodeBounds = false\n\n BitmapFactory.decodeResource(res, resId, this)\n }\n}\n```\n\n### Java\n\n```java\npublic static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,\n int reqWidth, int reqHeight) {\n\n // First decode with inJustDecodeBounds=true to check dimensions\n final BitmapFactory.Options options = new BitmapFactory.Options();\n options.inJustDecodeBounds = true;\n BitmapFactory.decodeResource(res, resId, options);\n\n // Calculate inSampleSize\n options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);\n\n // Decode bitmap with inSampleSize set\n options.inJustDecodeBounds = false;\n return BitmapFactory.decodeResource(res, resId, options);\n}\n```\n\nThis method makes it easy to load a bitmap of arbitrarily large size into an [ImageView](/reference/android/widget/ImageView) that displays a 100x100 pixel thumbnail, as shown in the following example\ncode: \n\n### Kotlin\n\n```kotlin\nimageView.setImageBitmap(\n decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100)\n)\n```\n\n### Java\n\n```java\nimageView.setImageBitmap(\n decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));\n```\n\nYou can follow a similar process to decode bitmaps from other sources, by substituting the\nappropriate [BitmapFactory.decode*](/reference/android/graphics/BitmapFactory#decodeByteArray(byte[], int, int, android.graphics.BitmapFactory.Options)) method as needed."]]