μ΄λ―Έμ§ μΊμλ₯Ό μ μ©νμ¬ μ΄λ―Έμ§ λ‘λ© μλλ₯Ό κ°μ νλ μν μ±μ λλ€. λΈλμΉ λ³λ‘ λ€μν μΊμ μ λ΅μ μ μ©ν΄λ³Ό μ μμ΅λλ€!
μλ νμΈμ! μ λ νμ¬ μ°ν μ½μμ μλλ‘μ΄λ κ°λ°μλ‘ "PokeRogueHelper" νλ‘μ νΈλ₯Ό μ§ννκ³ μμ΅λλ€.
μ±μμ νλ©΄μ μ΄λν λλ§λ€ "ν¬μΌλͺ¬ λ°μ΄ν°"λ₯Ό λΆλ¬μ€κ³ μ΄λ―Έμ§λ₯Ό λ λλ§νλ μμ μ΄ λΉλ²νκ² λ°μνλλ°, μ΄λ‘ μΈν΄ μ΄λ―Έμ§ λ‘λ© μλκ° λλ €μ§λ λ¬Έμ κ° λ°μνμ΅λλ€.
μ ν¬ νμ μΊμ(Cache)
λ₯Ό νμ©νμ¬ μ± μ±λ₯μ κ°μ νμμ΅λλ€. ν¬μΌλͺ¬μ΄λΌλ λλ©μΈ νΉμ±μ λ°μ΄ν°κ° μμ£Ό λ³κ²½λμ§ μκΈ° λλ¬Έμ, μΊμλ₯Ό μ μ©νμ¬ λ°μ΄ν° λ‘λ©κ³Ό μ΄λ―Έμ§ λλλ§ μλλ₯Ό ν¬κ² κ°μ ν μ μμμ΅λλ€.
μ΄λ² κΈμμλ λΉ λ₯΄κ³ ν¨μ¨μ μΈ λ°μ΄ν° λ‘λλ₯Ό μν΄ λ°λμ μμμΌ ν μΊμμ λν΄ μμλ³΄κ³ , μ΄λ―Έμ§ μΊμ μ€μ΅ μμ λ₯Ό ν΅ν΄ μΊμλ₯Ό μ§μ ꡬννλ λ°©λ²μ μκ°νκ² μ΅λλ€.
μΊμλ λ°μ΄ν°λ₯Ό μμλ‘ μ μ₯νλ μ₯μ
λ₯Ό μλ―Έν©λλ€.
μμ£Ό μ¬μ©νλ λ°μ΄ν°μ κ²½μ° λ§€λ² λ€νΈμν¬ ν΅μ μ ν΅ν΄ λ°μ΄ν°λ₯Ό λΆλ¬μ€λ κ²μ λΉν¨μ¨μ μ
λλ€. μ΅μ΄λ‘ λ°μ΄ν°λ₯Ό λΆλ¬μ¬ λ,μΊμ
μ λ°μ΄ν°λ₯Ό μ μ₯νμ¬ λ€ν¬μν¬ ν΅μ
μ μ€μ΄κ³ , λ°μ΄ν° λ‘λ© μλ
λ₯Ό κ°μ ν μ μμ΅λλ€.
μΊμλ μ μ₯ μμΉμ λ°λΌ λ©λͺ¨λ¦¬ μΊμ
μ λμ€ν¬ μΊμ
λ‘ λλ©λλ€.
λ΄κ° μ μ₯ν λ°μ΄ν°μ νΉμ±
μ λ°λΌ μ μ ν μΊμ λ°©λ²μ μ νν΄μΌν©λλ€.
λ©λͺ¨λ¦¬ μΊμ
λ RAM
μ μ μ₯λκΈ° λλ¬Έμ λΉ λ₯΄κ² λ°μ΄ν°λ₯Ό λΆλ¬μ¬ μ μμ§λ§, μ±μ΄ μ’
λ£λλ©΄ λ°μ΄ν°κ° μ¬λΌμ§λλ€.
λμ€ν¬ μΊμ
λ νλ λμ€ν¬
μ μ μ₯λκΈ° λλ¬Έμ μ±μ΄ μ’
λ£λμ΄λ λ°μ΄ν°κ° μ μ§λ©λλ€. νμ§λ§, λ©λͺ¨λ¦¬ μΊμμ λΉν΄ λλ¦¬κ² λ°μ΄ν°λ₯Ό λΆλ¬μ¬ μ μμ΅λλ€.
λ€μκ³Ό κ°μ κΈ°μ€μΌλ‘ μ μ ν μΊμ λ°©λ²μ μ νν μ μμ΅λλ€.
- λΉ λ₯΄κ² λ°μ΄ν°λ₯Ό λ‘λ©ν΄μΌν κ²½μ°:
λ©λͺ¨λ¦¬ μΊμ
- λ°μ΄ν°λ₯Ό μ₯κΈ°κ° μ μ₯νκ±°λ μ± μ¬μμ μ λ°μ΄ν°λ₯Ό μ μ§ν΄μΌν κ²½μ°:
λμ€ν¬ μΊμ
- λ°μ΄ν°κ° μ₯κΈ°κ° λ³κ²½λμ§ μλ κ²½μ°:
λμ€ν¬ μΊμ
- λ°μ΄ν°κ° μμ£Ό λ³κ²½λλ κ²½μ°:
λ©λͺ¨λ¦¬ μΊμ
- λ°μ΄ν°κ° μ€μκ°μΌλ‘ λ³κ²½λλ κ²½μ°:
μΊμ μ¬μ© X
μΊμλλ λ°μ΄ν°κ° λ무 λ§μμ Έ μΊμμ ν¬κΈ°λ₯Ό μ΄κ³Όνκ² λλ©΄ μ΄λ»κ² λ κΉμ? π€
μΊμμ μ μ₯λ λ°μ΄ν°κ° λ무 λ§μμ Έμ μΊμμ ν¬κΈ°λ₯Ό μ΄κ³Όνκ² λλ κ²½μ°λ₯Ό μΊμ μ€λ²νλ‘μ°(Cache Overflow)
λΌκ³ ν©λλ€.
μλ‘μ΄ λ°μ΄ν°λ₯Ό μΊμμ μ μ₯νκΈ° μν΄ μ μ₯λ λ°μ΄ν° μ€ μΌλΆλμμ
λμ΄μΌ ν©λλ€.
μ΄λ μ΄λ€ λ°μ΄ν°λ₯Ό λ¨Όμ μμ ν μ§ κ²°μ νλ λ°©λ²μ μΊμ κ΅μ²΄ μκ³ λ¦¬μ¦
μ΄λΌκ³ ν©λλ€.
μΊμ κ΅μ²΄ μκ³ λ¦¬μ¦ μ€ κ°μ₯ λ§μ΄ μ¬μ©λλ μκ³ λ¦¬μ¦μ LRU
μ LFU
μ
λλ€.
- LFU(Least Frequently Used): κ°μ₯ μ κ² μ¬μ©λ λ°μ΄ν°λ₯Ό μμ νλ λ°©μ
- LRU(Least Recently Used): κ°μ₯ μ€λ μ¬μ©λμ§ μμ λ°μ΄ν°λ₯Ό μμ νλ λ°©μ
LFU
λ λ°μ΄ν°μ μ°Έμ‘° λΉλμ
μ μκ±°ν μΊμ κ΅μ²΄ μκ³ λ¦¬μ¦μ
λλ€. λ§μ½ νΉμ λ°μ΄ν°κ° λ€λ₯Έ λ°μ΄ν°μ λΉν΄ λ μμ£Ό μ¬μ©λλ κ²½μ°μ LFU μκ³ λ¦¬μ¦μ΄ μ ν©ν©λλ€.
- ex)
νμ΄λ¦¬
λΌλ ν¬μΌλͺ¬ μ΄λ―Έμ§κ° λ€λ₯Έ ν¬μΌλͺ¬μ λΉν΄ 3 ~ 4 λ°° λ§μ΄ λλλ§λλ€λ©΄,νμ΄λ¦¬
λ λ€λ₯Έ ν¬μΌλͺ¬μ λΉν΄ λ μμ£Ό μ°Έμ‘°λλ λ°μ΄ν°μ λλ€.
LRU
λ μκ° μ§μμ±
μ μκ±°ν κ°μ₯ λ§μ΄ μ¬μ©λλ μΊμ κ΅μ²΄ μκ³ λ¦¬μ¦μ
λλ€. μκ° μ§μμ±
μ΄λ μ¬μ©μκ° κ°μ₯ μ΅κ·Όμ μ¬μ©ν λ°μ΄ν°κ° κ°μ₯ λμ νλ₯ λ‘ λ€μ μ¬μ©λ κ²μ΄λΌλ κ°λ
μ
λλ€.
- ex) μ¬μ©μκ° μ΅κ·Όμ
νΌμΉ΄μΈ
μ΄λ―Έμ§λ₯Ό μ¬μ©νλ€λ©΄, λ€μμλνΌμΉ΄μΈ
μ΄λ―Έμ§λ₯Ό μ¬μ©ν νλ₯ μ΄ λλ€λ κ²μ μλ―Έν©λλ€.
λ°μ΄ν°μ νΉμ±μ λ°λΌ μ μ ν μΊμ κ΅μ²΄ μκ³ λ¦¬μ¦μ μ ννμ¬ μΊμλ₯Ό κ΄λ¦¬ν΄λ©΄ λ©λλ€! πͺ
μ΄μ κ°λ΅νκ² μΊμμ λν΄ μμ보μμΌλ, μ€μ΅μ ν΅ν΄ μ΄λ―Έμ§ μΊμλ₯Ό ꡬνν΄λ³΄κ² μ΅λλ€!
μμΈν μ½λλ μ€μ΅ κΉνλΈ μ£Όμμμ νμΈν μ μμ΅λλ€!
μ΄λ―Έμ§ URL μ ν΅ν΄ μ΄λ―Έμ§λ₯Ό λΆλ¬μ νλ©΄μ λλλ§νλ κ°λ¨ν μνμ±μ λ§λ€μ΄λ³΄κ² μ΅λλ€.
class ImageLoader(
private val ImageService: PokemonImageService
) {
suspend fun bitmaps(urls: List<String>): List<Bitmap> {
return ImageService.bitmaps(urls)
}
}
ImageLoader
μμ ImageService
λ₯Ό ν΅ν΄ λ€νΈμν¬ ν΅μ νμ¬ ν¬μΌλͺ¬ μ΄λ―Έμ§λ₯Ό λΆλ¬μ€κ² μ΅λλ€.
μ΄λ―Έμ§ λ‘λ©μ΄ λ무 μ€λ걸리λ€μ. λ©λͺ¨λ¦¬ μΊμλ₯Ό μ μ©ν΄λ³΄κ² μ΅λλ€!
class ImageLoader(
private val imageService: ImageService
) {
private val cachedImages: MutableMap<String, Bitmap> = mutableMapOf<String, Bitmap>()
suspend fun bitmaps(urls: List<String>): List<Bitmap> {
if (cachedImages.keys.containsAll(urls.toSet())) {
return urls.map { requireNotNull(cachedImages[it]) }
}
return imageService.bitmaps(urls).also { cacheImages(urls, it) }
}
fun clearCache() {
cachedImages.clear()
}
private fun cacheImages(keys: List<String>, images: List<Bitmap>) {
keys.forEachIndexed { index, key ->
cachedImages[key] = images[index]
}
}
}
Map μλ£κ΅¬μ‘°λ₯Ό νμ©νμ¬ λ©λͺ¨λ¦¬ μΊμλ₯Ό ꡬννμμ΅λλ€.
cachedImages
μ μ΄λ―Έμ§κ° μΊμλμ΄ μλμ§ νμΈν ν, μΊμλμ΄ μμΌλ©΄ μΊμλ μ΄λ―Έμ§λ₯Ό λ°ν- κ·Έλ μ§ μμΌλ©΄ λ€νΈμν¬ ν΅μ μ ν΅ν΄ μ΄λ―Έμ§λ₯Ό λΆλ¬μ¨ ν, μΊμμ μ μ₯
μμ κ°μ λ°©μμΌλ‘ μ΄λ―Έμ§λ₯Ό μΊμνλ©΄, λμΌν μ΄λ―Έμ§ URL μ κ²½μ° λΉ λ₯΄κ² λ‘λν μ μμ΅λλ€!
λ°μ΄ν°λ₯Ό 리νλ μ¬νλλΌλ λ‘λ© νλ©΄μ΄ λ³΄μ΄μ§ μμ μ λλ‘ μ΄λ―Έμ§ λ‘λ©μ΄ λΉ¨λΌμ‘μ΅λλ€! κ·Έλ¬λ, μ±μ μ¬μμνλ©΄ μ΄λ¨κΉμ??
λ©λͺ¨λ¦¬ μΊμ λ°©μμ RAM
μ μΊμνκΈ° λλ¬Έμ νλ‘μΈμ€κ° μ’
λ£λλ©΄ μΊμκ° λΉμμ§κ²λ©λλ€.
λ°λΌμ, λ€νΈμν¬ ν΅μ κ³Ό λμΌνκ² μ΄λ―Έμ§λ₯Ό λ€μ λΆλ¬μμΌν©λλ€.
μ΄λ₯Ό ν΄κ²°νκΈ° μν΄ λμ€ν¬ μΊμ
λ₯Ό μ μ©ν΄λ³΄κ² μ΅λλ€!
μλλ‘μ΄λ λ΄λΆ μ μ₯μλ₯Ό νμ©νμ¬ PokemonImageSaver
ν΄λμ€μ μ΄λ―Έμ§λ₯Ό μ μ₯νκ³ λΆλ¬μ€λ κΈ°λ₯μ μΆκ°νμ΅λλ€.
class ImageSaver(context: Context) {
private val cacheFolder: File = File(context.cacheDir, "pokemon")
get() {...}
suspend fun bitmaps(urls: List<String>): List<Bitmap> = withContext(Dispatchers.IO) {
urls.mapNotNull { url ->
val file = photoCacheFile(url)
if (file.exists()) {
BitmapFactory.decodeFile(file.absolutePath)
} else {
null
}
}
}
suspend fun saveImage(url: String, bitmap: Bitmap) = withContext(Dispatchers.IO) {
photoCacheFile(url).outputStream().use { output ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)
}
}
saveImage
ν¨μμμλ Bitmap μ PNG νμμΌλ‘ μμΆνμ¬ λ΄λΆ μ μ₯μμ μ μ₯ν©λλ€.bitmaps
ν¨μμμλurls
μ ν΄λΉνλ μ΄λ―Έμ§ νμΌμ΄ μ‘΄μ¬νλ©΄ Bitmap μΌλ‘ λμ½λ©νμ¬ λ°νν©λλ€.
class ImageLoader(
private val imageService: ImageService,
private val imageSaver: ImageSaver
) {
private val cachedImages: MutableMap<String, Bitmap> = mutableMapOf<String, Bitmap>()
suspend fun bitmaps(urls: List<String>): List<Bitmap> {
if (isMemoryCached(urls)) {
return urls.map { requireNotNull(cachedImages[it]) }
}
if (isDiskCached(urls)) {
return imageSaver.bitmaps(urls).also { cacheImages(urls, it) }
}
return imageService.bitmaps(urls)
.also { bitmap ->
urls.zip(bitmap).forEach { (url, bitmap) ->
imageSaver.saveImage(url, bitmap)
}
}
.also { bitmap ->
cacheImages(urls, bitmap)
}
}
...
μ΄μ PokemonImageLoader μμ λ©λͺ¨λ¦¬ μΊμμ λμ€ν¬ μΊμλ₯Ό λͺ¨λ νμ©νμ¬ μ΄λ―Έμ§λ₯Ό λΆλ¬μ΅λλ€.
- λ©λͺ¨λ¦¬ μΊμμ μ΄λ―Έμ§κ° μ‘΄μ¬νλμ§ νμΈ
- λμ€ν¬ μΊμμ μ΄λ―Έμ§κ° μ‘΄μ¬νλμ§ νμΈ
- λ€νΈμν¬ ν΅μ μ ν΅ν΄ μ΄λ―Έμ§λ₯Ό λΆλ¬μ¨ ν, λμ€ν¬ μΊμ, λ©λͺ¨λ¦¬ μΊμμ μ μ₯
μ΄μ μ±μ μ¬μμν΄λ λμ€ν¬λ μ΄λ―Έμ§λ₯Ό λΆλ¬μ λΉ λ₯΄κ² νλ©΄μ λ‘λν μ μμ΅λλ€.
λ΄λΆ μ μ₯μλ RAM 곡κ°μ΄ λΆμ‘±ν΄μ§ κ²½μ°, μΊμλ μ΄λ―Έμ§ μ€ μΌλΆλ₯Ό μμ
ν ν μλ‘μ΄ μ΄λ―Έμ§λ₯Ό μ μ₯ν΄μΌν©λλ€.
λ©λͺ¨λ¦¬ μΊμ
μ κ²½μ° μλλ‘μ΄λμμ μ 곡νλ LRUCache
λ₯Ό νμ©νμ¬ LRU μΊμ κ΅μ²΄ μκ³ λ¦¬μ¦μ μ½κ² μ μ©ν μ μμ΅λλ€.
mutableMap
μ LRUCache
λ‘ λ³κ²½ν΄μ£Όκ² μ΅λλ€!
class ImageLoader(
...
) {
private val cachedImages: LruCache<String, Bitmap> =
lruCache(cacheSize(), sizeOf = { _, value -> value.byteCount / 1024 })
private fun cacheSize(): Int {
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
return maxMemory / 8
}
}
lruCache() ν©ν 리 ν¨μλ₯Ό ν΅ν΄ LRUCache
λ₯Ό μμ±νμμ΅λλ€.
cacheSize()
ν¨μλ₯Ό ν΅ν΄ μΊμμ μ΅λ ν¬κΈ° λ©λͺ¨λ¦¬μ1/8
λ‘ μ€μ νμμ΅λλ€.sizeOf
λ μΊμμ μ μ₯λ λ°μ΄ν°νλ
μ ν¬κΈ°λ₯Ό κ³μ°νλ λλ€μ λλ€.
λ§μ½, μΊμμ μΆκ°λ Bitmap μ byteCount
κ° 1024
λ³΄λ€ ν¬λ€λ©΄, 1KB
λ¨μλ‘ μΊμμ μ μ₯λ©λλ€.
lruCache() μ μ¬μ©λ²μ΄ κΆκΈνμλ€λ©΄ lruCache νμ΅ν
μ€νΈ λ₯Ό μ°Έκ³ ν΄μ£ΌμΈμ!
μ
maxMemory / 8
λ‘ μ€μ νμκΉμ? π€μΌλ°/hdpi κΈ°κΈ°μ κ²½μ° μ΅μ
32MB
μ λ©λͺ¨λ¦¬λ₯Ό μ 곡νμ¬,maxMemory / 8
μ λλ‘ μΊμλ₯Ό μ€μ νλ κ²μ κΆμ₯νκ³ μμ΅λλ€.λ§μ½, 800x480 ν΄μλμ κΈ°κΈ°μμ μ΄λ―Έμ§λ‘λ§ κ΅¬μ±λ GridView κ° νλ©΄μ κ½ μ±μΈ κ²½μ°, μ½
1.5MB
μ λ©λͺ¨λ¦¬κ° νμνλ€κ³ ν©λλ€. λ°λΌμ, 32MB / 8 = 4MB μ λλ‘ μ€μ νλ©΄ μ½2.5 Page
μ ν΄λΉνλ μ΄λ―Έμ§λ₯Ό μΊμν μ μμ΅λλ€.
λμ€ν¬ μΊμ
μ κ²½μ°, μλλ‘μ΄λμμ 곡μμ μΌλ‘ μ§μνλ DiskLruCache
λ μμ΅λλ€. π’
λ°λΌμ, java μ File I/O
λ₯Ό νμ©νμ¬ LRU μΊμ κ΅μ²΄ μκ³ λ¦¬μ¦μ μ§μ ꡬννμμ΅λλ€.
μ΄λ κ°μ₯ μ€λμ μ μ κ·Όν νμΌ
μ μ°Ύμ κ΅μ²΄νκΈ° μν΄ νμΌμ lastModified
λ₯Ό νμ¬ μκ°μΌλ‘ μ
λ°μ΄νΈνλ updateFileAccessTime
ν¨μλ₯Ό ꡬννμμ΅λλ€.
private fun updateFileAccessTime(file: File) {
file.setLastModified(System.currentTimeMillis())
}
κ·Έλ¦¬κ³ , λμ€ν¬ μΊμμ ν¬κΈ°κ° MAX_DISK_CACHE_SIZE
λ₯Ό μ΄κ³Όνλ©΄, κ°μ₯ μ€λμ μ μ κ·Όν νμΌ
λΆν° μμ νλ manageDiskCacheSize()
ν¨μλ₯Ό ꡬννμμ΅λλ€.
private fun manageDiskCacheSize() {
val files = cacheFolder.listFiles() ?: return
var totalSize = files.sumOf { it.length() }
val maxSize = MAX_DISK_CACHE_SIZE
if (totalSize > maxSize) {
val sortedFiles = files.sortedBy { it.lastModified() }
for (file in sortedFiles) {
if (totalSize <= maxSize) break
totalSize -= file.length()
file.delete()
}
}
}
νμΌμ μ½μ λλ§λ€ updateFileAccessTime()
ν¨μλ₯Ό ν΅ν΄ νμΌμ lastModified
λ₯Ό μ
λ°μ΄νΈνκ³ ,
manageDiskCacheSize()
ν¨μλ₯Ό νΈμΆνμ¬ μΊμμ ν¬κΈ°λ₯Ό κ΄λ¦¬νλλ‘ νμμ΅λλ€.
μ΄μ λ©λͺ¨λ¦¬ κ³Ό λμ€ν¬ μΊμ
λͺ¨λ LRU μΊμ κ΅μ²΄ μκ³ λ¦¬μ¦μ μ μ©νμ¬ μΊμ μ€λ²νλ‘μ°
λ₯Ό λ°©μ§ν μ μμ΅λλ€!
μλλ‘μ΄λμμ μΊμ±
μ ν΅ν΄ λ°μ΄ν° λ‘λ© μκ°μ μ€μ΄κ³ , λ€νΈμν¬ μμ²μ μ€μ¬ μ±λ₯μ ν₯μμμΌ μ¬μ©μ κ²½ν ν₯μμ μ€μν μν μ ν©λλ€.
μ΄λ² κΈμμλ λ©λͺ¨λ¦¬ μΊμμ λμ€ν¬ μΊμ
λ₯Ό νμ©νμ¬ μ΄λ―Έμ§ μΊμλ₯Ό ꡬννλ λ°©λ²κ³Ό μΊμ κ΅μ²΄ μκ³ λ¦¬μ¦
μ μ μ©νμ¬ μΊμ μ€λ²νλ‘μ°λ₯Ό λ°©μ§νλ λ°©λ²μ μμ보μμ΅λλ€.
μλλ‘μ΄λ κ°λ°μλΌλ©΄ Glide λ Picasso μ κ°μ μ΄λ―Έμ§ λ‘λ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νλ©΄ λλλ° μ§μ ꡬνν μΌμ΄ μμ κ²μ΄λΌκ³ μκ°ν μ μμ΅λλ€.
νμ§λ§, μ΄λ² κΈμ ν΅ν΄ μΊμλ₯Ό μ§μ ꡬνν΄λ³΄λ©΄μ μΊμμ λμ μ리λ₯Ό μ΄ν΄νμλ κ²μ΄ μΆν μ°λ¦¬μ μλΉμ€μ ν¨μ¨μ μΈ μΊμ μ λ΅μ μ ννκ³ μ μ©νλ λ° λμμ΄ λ κ²μ λλ€! π
κ°μ¬ν©λλ€! πΉ
https://developer.android.com/topic/performance/graphics/cache-bitmap