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