안드로이드 Glide 없이 LruCache로 이미지 캐싱하기 (Bitmap Cache)

임현주·2022년 9월 28일
0
post-thumbnail

시작

혼자 공부하면서 정리하고 싶은 부분을 작성한 글입니다 👀
함께 공부하는 사람에게는 도움이 되었으면 좋겠고,
혹시 잘못 이해하고 작성한 부분에 대한 피드백을 주신다면 감사히 받겠습니다 🙇🏻‍♀️


이미지 로딩을 위해 동일한 이미지를 매번 다운로드 하는 것은 비효율적이다. 한 번 다운로드 했던 이미지는 임시 저장했다가 재사용할 수 있도록 캐싱을 해주는 것이 일반적❗️ Glide 라이브러리에서는 이러한 캐싱 뿐만 아니라 다양한 기능을 제공해주기 때문에 항상 사용해왔다.

하지만 "Android : org.jetbrains. , androidx. 외에 다른 라이브러리는 추가하지 않습니다." 라는 제한이 걸린다면❓ 실제로 프로그래머스 과제 테스트의 제한 사항이었다. 기본의 중요성을 생각해보며.. Glide 없이 이미지 캐싱을 진행해보자 🙋🏻‍♀️❗️

LruCache

LruCache는 LRU 알고리즘에 대한 구현체로, 안드로이드에서 캐시를 관리하기 위해 제공하는 메모리 캐시 객체다.

잠깐! LRU 알고리즘이란? 🤷🏻‍♀️


LRU (Least Recently Used)
가장 오랜 시간 사용되지 않은(참조되지 않은) 페이지를 교체하는 기법
메모리를 관리하는 운영체제에서 페이지 부재가 발생하여 새로운 페이지를 할당하기 위해 현재 할당된 페이지 중 어느것과 교체할지 결정하는 방법

➡️ 최근에 조회된 것을 캐시에서 삭제하는 것을 늦추기 위한 것으로, 오랫동안 접근되지 않은 메모리가 우선적으로 삭제된다.

public class LruCache<K, V> { // 제네릭으로 선언됨, <K : 캐시에 접근하기 위한 키 값, V : 캐시에서 가져올 객체 타입>

    public LruCache(int maxSize) { // 생성자 인자로 maxSize를 int 값으로 받음
        throw new RuntimeException("Stub!");
    }
    
    . . .
}

필자는 < Key : 이미지 url(String), Value : Bitmap > 으로 설정하여 LruCache를 익명 객체로 선언하였다. 생성자 인자에 넣어줄 캐시 사이즈는 전체 메모리 사이즈 중 1/8로 설정했다.

// 전체 메모리 사이즈 중 1/8을 LruCache 사이즈로 잡는다.
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
val cacheSize = maxMemory / 8
val bitmapCache = object : LruCache<String?, Bitmap?>(cacheSize) {
    override fun sizeOf(key: String?, value: Bitmap?): Int {
        return value?.byteCount!! / 1024
    }
}

. . .

// 만약 일반 클래스로 선언하고 싶다면 아래와 같이 선언해주면 된다.
class BitmapLruCache(cacheSize: Int) : LruCache<String?, Bitmap?>(cacheSize) {
    override fun sizeOf(key: String?, value: Bitmap?): Int {
        return value?.byteCount!! / 1024
    }
}

LruCache에 Bitmap 저장하기

bitmapCache.put(url, bitmap)

LruCache에서 Bitmap 가져오기

val bitmap: Bitmap? = bitmapCache.get(url)

만약 bitmap의 변수가 null 값이라면 캐시에 값이 없다는 것이므로 이를 이용해 조건문으로 우리가 원하는 처리를 해주면 된다.


object ImageLoader {

    private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
    val cacheSize = maxMemory / 8
    private val bitmapCache = object : LruCache<String?, Bitmap?>(cacheSize) {
        override fun sizeOf(key: String?, value: Bitmap?): Int {
            return value?.byteCount!! / 1024
        }
    }

    fun loadImage(url: String, completed: (Bitmap?) -> Unit) {
        if (url.isEmpty()) {
            completed(null)
            return
        }

        GlobalScope.launch(Dispatchers.IO) {
            try {
                var bitmap: Bitmap? = bitmapCache.get(url)

                if (bitmap == null) {
                    bitmap = BitmapFactory.decodeStream(URL(url).openStream())
                    bitmapCache.put(url, bitmap)
                }

                withContext(Dispatchers.Main) {
                    completed(bitmap)
                }
            } catch (e: Exception) {
                withContext(Dispatchers.Main) {
                    completed(null)
                }
            }
        }
    }

}

loadImage() 는 url로 이미지를 로딩하기 위한 메소드이다.

  • url을 조회하여 캐시에 해당 Bitmap이 있으면 이를 반환하고
  • 없으면 String(Url) ➡️ Bitmap 변환 처리를 해준 후 캐시에 저장을 해주고 이를 반환해준다.
    (변환 처리를 해주는 과정에서 Bitmap resize를 해주는 것도 좋다.)

( + 이미지 다운로드 자체가 네트워크를 사용하는 IO 작업이기 때문에 UI Thread에서 처리하면 안되는거 아시쥬..?! UI Thread에서 네트워크까지 처리해버리면 응답이 언제 올지 모르고.. 시간은 계속 지체되고.. ANR(앱이 일정 시간 내에 응답하지 못해 발생) 에러를 경험하게 될 것입니다.. 🥶 )


깨달음

"Android : org.jetbrains. , androidx. 외에 다른 라이브러리는 추가하지 않습니다."

문구를 보고 순간 얼어붙었던 나..

결국 이 라이브러리도 기본 기능들을 조합해 만들어진 것인데, 라이브러리에게 맡기는 것을 당연하게 생각하다보니 정작 중요한 것을 놓치고 있었던 것 같다. 이번 기회에 크게 반성했다 🥲

언제나 중요한 것은 기본.. 기본을 열심히 해두자 ✍🏻

profile
🐰 피드백은 언제나 환영합니다

0개의 댓글