# 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`