hong's android

[안드로이드] Lru Cache 본문

Android/Android

[안드로이드] Lru Cache

_hong 2023. 2. 21. 15:28

Lru Cache(Least Recently Used Cache) 란?

가장 오랫동안 참조되지 않은 값(노드)을 삭제하고 비교적 최근에 참조된 값(노드)들을 저장하는 캐시 방식

 

Double Linked List를 기본적으로 사용한다. Head에 가까운 노드일수록 최근에 참조된 노드, tail에 가까울수록 참조가 오랫동안 되지 않은 노드이다. 만약 정해진 cache size가 초과된 상황에서 노드(값)를 추가할 때 tail에 가장 가까운 노드가 삭제되고, 새로운 참조 노드가 head에 가깝게 추가된다.

 

안드로이드에선 Lru Cache방식을 사용하기 위한 관련 클래스를 제공한다.

 

캐시 크기 설정시 주의점

모든 애플리케이션에 적합한 특정 크기나 수식은 없으며 사용량을 분석하여 적합한 해결책을 찾아야 합니다. 캐시가 너무 작으면 아무런 이점 없이 추가 오버헤드가 발생하고 캐시가 너무 크면 또다시 java.lang.OutOfMemory 예외가 발생하여 앱의 나머지 부분에 사용할 메모리가 거의 남아 있지 않을 수 있습니다.

 

 

비트맵 캐싱  |  Android 개발자  |  Android Developers

단일 비트맵을 사용자 인터페이스(UI)에 로드하는 것은 간단하지만 한 번에 더 큰 이미지의 집합을 로드해야 하면 더 복잡해집니다. 많은 경우(ListView, GridView 또는 LruCache 클래스와 같은 구성요소

developer.android.com

 

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var memoryCache : LruCache<String, Bitmap>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        initCache()

        binding.button.setOnClickListener {
            loadBitmap("https://picsum.photos/200",binding.imageView)
        }
    }

    fun initCache(){
        // Get max available VM memory, exceeding this amount will throw an
        // OutOfMemory exception. Stored in kilobytes as LruCache takes an
        // int in its constructor.
        val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()

        // Use 1/8th of the available memory for this memory cache.
        val cacheSize = maxMemory / 8

        memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
            override fun sizeOf(key: String, bitmap: Bitmap): Int {
                // The cache size will be measured in kilobytes rather than
                // number of items.
                return bitmap.byteCount / 1024
            }
        }
    }

    fun loadBitmap(url: String, mImageView: ImageView) {
        val bitmap: Bitmap? = memoryCache.get(url)?.also {
            mImageView.setImageBitmap(it)
            Log.d("image","From memory")

        } ?: run {
            Log.d("image","From internet")
            val task = BitmapWorkerTask()
            task.setImageView(mImageView)
            task.execute(url)
            null
        }
    }

    private inner class BitmapWorkerTask : AsyncTask<String, Unit, Int>() {

        private lateinit var mImageView : ImageView
        private var bmp: Bitmap? = null

        fun setImageView(imageview: ImageView) {
            mImageView = imageview
        }

        // Decode image in background.
        override fun doInBackground(vararg params: String?): Int {
            var url = params[0]

            try {
                bmp = getBitmapFromURL(url)

                if(bmp != null){
                    memoryCache.put(url,bmp)
                    Log.d("image","save image to cache")
                }
            } catch (e : Exception) {
                e.printStackTrace()
                0
            }

            return 1
        }

        override fun onPostExecute(result: Int) {
            if(result == 1){
                mImageView.setImageBitmap(bmp)
            }

            super.onPostExecute(result)
        }

        private fun getBitmapFromURL(url: String?): Bitmap? {
            return try {
                val url = URL(url)
                val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
                connection.setDoInput(true)
                connection.connect()
                val input: InputStream = connection.getInputStream()
                BitmapFactory.decodeStream(input)
            } catch (e: IOException) {
                e.printStackTrace()
                null
            }
        }


    }
}

 

처음 이미지 load 

 

같은 이미지 다시 load