---
# System prepended metadata

title: LRU(Least Recently Used Cache) for Bitmap
tags: [cache, lru, Android, kotlin]

---

# LRU(Least Recently Used Cache) for Bitmap 

### 紀錄lru cache在recycler View上的使用方式
#### 定義類別如下
```kotlin=
class ImageLoader() {
    // use memory cache
    private var memoryCache: LruCache<String, Bitmap>
    private var cacheSize by Delegates.notNull<Int>()

    init { 
    //取得app被分配的記憶體大小，再取1/8當作快取，這可隨需求調整
        val maxMemoty = (Runtime.getRuntime().maxMemory() / 1024).toInt()
        cacheSize = maxMemoty / 8
        memoryCache = object : LruCache<String, Bitmap>(cacheSize) {
            override fun sizeOf(key: String?, value: Bitmap): Int {
                return value.byteCount / 1024
            }
        }
    }
    
    //定義新增、查詢、刪除等方法
    fun addBitmapToMemoryCache(key: String, bitmap: Bitmap){
        if (getBitmapFromMemCache(key) == null){
            memoryCache.put(key,bitmap)
        }
    }

    fun getBitmapFromMemCache(key: String): Bitmap?{
        return memoryCache.get(key)
    }

    fun removeBitmapFromMenCache(key: String){
        memoryCache.remove(key)
    }
}
```

#### 在recycler View Adapter使用快取
##### 因為在onBindViewHolder會取得要綁定的viewHolder& data position，在這邊決定從網路下載圖片或者從快取拿圖片
```kotlin=

    //在adapter頂層先實例化 圖片快取
    val mLoader = ImageLoader()
    
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = data[position]

        val key: String = "${data[position]._id}" //這邊使用資料庫的primary key當作快取的key
        val bmp: Bitmap? = mLoader.getBitmapFromMemCache(key) //這邊查詢快取內有沒有這個key對應的value
        if (bmp == null) { //快取沒有資料
            Timber.d("快取是空的，從網路解析圖片")
            //從網路下載
            getBitmap(data[position].image.toString(), key, position)
            holder.bindData(item)  // 這邊使用函數的多載，當圖片還沒下載完成，先綁定資料
        } else { //快取有資料
            Timber.d("有資料，從快取拿圖片")
            holder.bindData(item, bmp) // 這邊使用函數的多載，從快取拿到圖片後，跟資料一起綁定
        }
    }
     
     
        private fun getBitmap(url: String, key: String, pos : Int) {
        var result: Bitmap?
        //做bitmap decode ＆ update UI
        GlobalScope.launch(Dispatchers.Main) {
            try {
                val job: Deferred<Bitmap> = async(Dispatchers.IO) {
                    val myurl = URL(url)
                    val connection = myurl.openConnection() as HttpURLConnection
                    connection.doInput = true
                    connection.connect()
                    val ins = connection.inputStream
                    BitmapFactory.decodeStream(ins)
                }
                result = job.await()
                //寫入快取
                result?.let { mLoader.addBitmapToMemoryCache(key, it) }

            } catch (e: Exception) {
                Timber.d("error = ${e.message}")
            }
            notifyItemChanged(pos) //update UI in main Thread
        }
    }


```
#### 「更新」＿將下載圖片功能移動至viewModel，讓adapter純粹接收資料與顯示
##### 底下使用bitMapFactory option進行圖片縮放與解碼，減少記憶體佔用
```kotlin=

//實例化 圖片快取
    val mLoader = ImageLoader()
    
    fun getImgFromUrl(source: List<BookStore>) {
        val newSource = source
        val result = HashMap<String, Bitmap>()
        for (i in newSource.indices) {
            val key: String = newSource[i]._id.toString()//for cache's key
            if (mLoader.getBitmapFromMemCache(key) == null) {
                //如果快取沒有對應_id的Bitmap就從網路下載
                viewModelScope.launch {
                    try {
                        val job: Deferred<ResponseBody> = async(Dispatchers.IO) {
                            BookApi.retrofitService.getImg()
                        }
                        job.await().let {
                            val bmp = bmpDecode(it) //decode to Bitmap
                            mLoader.addBitmapToMemoryCache(key, bmp)  // write in cache
                            result[key] = bmp  //put into hashMap
                            _imgResult.value = result  //set value to LiveData
                        }
                    } catch (e: Exception) {
                        Timber.d("發生錯誤，${e.message}")
                    }
                }

            } else {
                //快取有圖片的話 直接拿
                val bmp = mLoader.getBitmapFromMemCache(key)
                bmp?.let { result[key] = it }
                _imgResult.value = result
            }
        }
    }
    
    /**
     * ResponseBody decode to Bitmap
     */
    private fun bmpDecode(source: ResponseBody): Bitmap {
        val option = BitmapFactory.Options().apply {
            inJustDecodeBounds = true  //設為true，不將圖片載入記憶體，但可以獲取圖片的寬高pixel
        }
        val data: ByteArray = source.bytes()
        BitmapFactory.decodeByteArray(data, 0, data.size, option)
        option.inSampleSize = calculateInSampleSize(option, 200, 200)
        option.inJustDecodeBounds = false //將圖片進行縮放之後，讀進記憶體內
        return BitmapFactory.decodeByteArray(data, 0, data.size, option)
    }

    /**
     * 根據輸入目標長寬計算出取樣大小
     */
    private fun calculateInSampleSize(
        options: BitmapFactory.Options,
        targetWidth: Int,
        targetHeigth: Int
    ): Int {
        val heigth: Int = options.outHeight  //640
        val width: Int = options.outWidth   // 480
        var inSampleSize = 1
        if (heigth > targetHeigth || width > targetWidth) {
            val halfHeigth = heigth / 2
            val halfWidth = width / 2
            while ((halfHeigth / inSampleSize) >= targetHeigth && (halfWidth / inSampleSize) >= targetWidth) {
                inSampleSize *= 2
            }
        }
        return inSampleSize
    }

```


###### tags: `cache` `lru` `kotlin` `Android` 