Development/Android

[Android] Out Of Memory를 대비하여 큰 Bitmap 효율적으로 처리하기

SeungYong.Lee 2024. 2. 23. 17:09
반응형
Fatal Exception: java.lang.IllegalArgumentException
RemoteViews for widget update exceeds maximum bitmap memory usage (used: 16216160, max: 15940800)

위와 같은 에러가 간혹 Widget에서 Bitmap을 사용할 때 발생합니다.

이전에 관련된 글을 작성했으나 여전히 재발하는 경우가 있어서 더 강화된 로직을 문서를 참고하여 구성했습니다.

 

거대한 비트맵 처리에 대해서 아래 공식 문서에서 처리 로직을 제안해주고 있습니다.

https://developer.android.com/topic/performance/graphics/load-bitmap?hl=ko

 

큰 비트맵을 효율적으로 로드  |  App quality  |  Android Developers

이미지의 모양과 크기는 다양합니다. 많은 경우 이미지는 일반적인 애플리케이션 사용자 인터페이스(UI)에서 요구하는 크기보다 큽니다. 예를 들어, 시스템 Gallery 애플리케이션은 Android 기기의

developer.android.com

 

너비와 높이 계산만을 위한 옵션을 설정하고, 첫 번째 디코딩을 진행합니다.

val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeFile(file.absolutePath, options)

 

이후, Sample Size를 통해 적절한 크기로 리사이징 및 샘플링이 가능합니다.

특정 한계 값 아래로 Bitmap Sample Size가 형성되도록 함수를 구성합니다.
전 위젯 생성 시 선언한 기본 가로 세로 값을 활용했습니다.

private fun calculateInSampleSize(
    options: BitmapFactory.Options,
    widgetHeight: Int,
    widgetWidth: Int): Int
{
    val height = options.outHeight
    val width = options.outWidth
    var inSampleSize = 1
    if (height > widgetHeight || width > widgetWidth) {
        val halfHeight = height / 2
        val halfWidth = width / 2

        while (halfHeight / inSampleSize >= widgetHeight
            || halfWidth / inSampleSize >= widgetWidth
        ) { inSampleSize *= 2 }
    }
    return inSampleSize
}

 

너비와 높이를 계산한 이후 옵션의 'inJsutDecodeBounds'를 다시 false 하여 최종 디코딩을 진행합니다.

options.inSampleSize = calculateInSampleSize(options, width, height)
options.inJustDecodeBounds = false
BitmapFactory.decodeFile(file.absolutePath, options)

 

전체 코드 ->

val bitmap = try {
    val file = File("${context.filesDir.absolutePath}/$example")
    if (file.exists()) {
        val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
        BitmapFactory.decodeFile(file.absolutePath, options)
        options.inSampleSize = calculateInSampleSize(options, width, height)
        options.inJustDecodeBounds = false
        BitmapFactory.decodeFile(file.absolutePath, options)
    } else {
        null
    }
} catch (e: Exception) {
    e.printStackTrace()
    null
} ?: return
반응형