Kache

キャッシュライブラリKotlinマルチプラットフォームAndroidiOSメモリキャッシュファイルキャッシュ

キャッシュライブラリ

Kache

概要

KacheはKotlin Multiplatformに対応した軽量キャッシュライブラリで、メモリ内キャッシュとファイルベースの永続キャッシュの両方をサポートし、複数の排除戦略(LRU、FIFO、MRU、FILO)を提供します。

詳細

KacheはMayakaAppsが開発したKotlin Multiplatform対応のキャッシュライブラリです。Android、iOS、デスクトップアプリケーションで同一のAPIを使用できるため、クロスプラットフォーム開発において統一されたキャッシュソリューションを提供します。メモリ内キャッシュとファイルベースの永続キャッシュの両方をサポートし、LRU(Least Recently Used)、FIFO(First In First Out)、MRU(Most Recently Used)、FILO(First In Last Out)の4つの排除戦略から選択できます。Kotlin Coroutinesに完全対応し、非同期処理でブロッキングを回避します。type-safeなAPIデザインにより、コンパイル時の型チェックを活用できます。ファイルキャッシュでは自動的なファイル管理とクリーンアップ機能を提供し、メモリ使用量の制限やキャッシュサイズの設定が可能です。シンプルで直感的なAPIながら、高いパフォーマンスと柔軟性を両立しています。

メリット・デメリット

メリット

  • マルチプラットフォーム対応: Android、iOS、デスクトップで同一API使用可能
  • 豊富な排除戦略: LRU、FIFO、MRU、FILOの4つの戦略をサポート
  • メモリ・ファイルキャッシュ: 用途に応じて最適なキャッシュタイプを選択
  • Coroutines対応: 非同期処理でブロッキング回避
  • Type Safe: Kotlinの型安全性を活用した設計
  • 軽量設計: 最小限の依存関係とオーバーヘッド
  • 自動管理: ファイルキャッシュの自動クリーンアップ機能

デメリット

  • 新しいライブラリ: 比較的新しく、大規模な実績が限定的
  • Kotlin限定: Kotlin以外の言語では使用不可
  • ドキュメント不足: 詳細なドキュメントやサンプルが限定的
  • エコシステム: 他のキャッシュライブラリと比べて周辺ツールが少ない
  • 複雑な機能制限: Redis等の高度な機能(Pub/Sub、Cluster等)は非対応

主要リンク

書き方の例

Gradle依存関係の設定

// build.gradle.kts (共通モジュール)
dependencies {
    // メモリ内キャッシュ
    implementation("com.mayakapps.kache:kache:2.1.0")
    
    // ファイルベースキャッシュ
    implementation("com.mayakapps.kache:file-kache:2.1.0")
}

基本的なメモリキャッシュ操作

import com.mayakapps.kache.Kache
import com.mayakapps.kache.KacheStrategy

// メモリキャッシュの初期化
val cache = Kache<String, ByteArray>(
    maxSize = 5 * 1024 * 1024 // 5MB
) {
    strategy = KacheStrategy.LRU
}

// データの保存と取得
suspend fun cacheExample() {
    val key = "user_profile_123"
    
    // キャッシュからデータを取得、存在しない場合は新しく作成
    val userData = cache.getOrPut(key) {
        // 重い処理(API呼び出し、データベースアクセス等)
        fetchUserDataFromApi(key)
    }
    
    // 直接値を設定
    cache.put("simple_key", "simple_value".toByteArray())
    
    // 値の取得
    val cachedValue = cache.get("simple_key")
    println("Cached value: ${cachedValue?.let { String(it) }}")
    
    // キャッシュサイズとヒット率の確認
    println("Cache size: ${cache.size}")
    println("Hit ratio: ${cache.hitRatio}")
}

ファイルベースキャッシュ操作

import com.mayakapps.kache.FileKache
import com.mayakapps.kache.KacheStrategy
import java.io.File

// ファイルキャッシュの初期化
val fileCache = FileKache(
    directory = File("cache"),
    maxSize = 50 * 1024 * 1024 // 50MB
) {
    strategy = KacheStrategy.LRU
}

suspend fun fileCacheExample() {
    val imageUrl = "https://example.com/image.jpg"
    val cacheKey = "image_${imageUrl.hashCode()}"
    
    try {
        // ファイルキャッシュからデータを取得、存在しない場合はダウンロード
        val imageFile = fileCache.getOrPut(cacheKey) { tempFile ->
            try {
                // ファイルをダウンロードして一時ファイルに保存
                downloadImageToFile(imageUrl, tempFile)
                true // 成功時はtrue
            } catch (e: Exception) {
                false // 失敗時はfalse(ファイルは自動削除される)
            }
        }
        
        if (imageFile != null) {
            println("Image cached at: ${imageFile.absolutePath}")
            // ファイルを使用
            displayImage(imageFile)
        }
    } finally {
        // リソースのクリーンアップ
        fileCache.close()
    }
}

異なる排除戦略の使用

import com.mayakapps.kache.Kache
import com.mayakapps.kache.KacheStrategy

class CacheExamples {
    
    // LRU (Least Recently Used) - 最も使用頻度の低いものを削除
    private val lruCache = Kache<String, UserData>(maxSize = 100) {
        strategy = KacheStrategy.LRU
    }
    
    // FIFO (First In First Out) - 最も古いものを削除
    private val fifoCache = Kache<String, ProductData>(maxSize = 200) {
        strategy = KacheStrategy.FIFO
    }
    
    // MRU (Most Recently Used) - 最も使用頻度の高いものを削除
    private val mruCache = Kache<String, TempData>(maxSize = 50) {
        strategy = KacheStrategy.MRU
    }
    
    // FILO (First In Last Out) - 最も新しいものを削除
    private val filoCache = Kache<String, LogData>(maxSize = 300) {
        strategy = KacheStrategy.FILO
    }
    
    suspend fun demonstrateStrategies() {
        // LRUキャッシュの使用例
        val user = lruCache.getOrPut("user_123") {
            UserData("John", "[email protected]")
        }
        
        // FIFOキャッシュの使用例
        fifoCache.put("product_456", ProductData("Laptop", 999.99))
        
        // キャッシュ統計の表示
        println("LRU Cache - Size: ${lruCache.size}, Hit Ratio: ${lruCache.hitRatio}")
        println("FIFO Cache - Size: ${fifoCache.size}, Hit Ratio: ${fifoCache.hitRatio}")
    }
}

data class UserData(val name: String, val email: String)
data class ProductData(val name: String, val price: Double)
data class TempData(val value: String)
data class LogData(val message: String, val timestamp: Long)

Android専用の画像キャッシュ実装

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.mayakapps.kache.Kache
import com.mayakapps.kache.FileKache
import com.mayakapps.kache.KacheStrategy
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.net.URL

class ImageCacheManager(private val context: Context) {
    
    // メモリキャッシュ(デコード済みBitmap用)
    private val memoryCache = Kache<String, Bitmap>(
        maxSize = 20 * 1024 * 1024 // 20MB
    ) {
        strategy = KacheStrategy.LRU
    }
    
    // ファイルキャッシュ(元画像データ用)
    private val fileCache = FileKache(
        directory = File(context.cacheDir, "images"),
        maxSize = 100 * 1024 * 1024 // 100MB
    ) {
        strategy = KacheStrategy.LRU
    }
    
    suspend fun loadImage(url: String, maxWidth: Int, maxHeight: Int): Bitmap? {
        val cacheKey = "${url}_${maxWidth}x${maxHeight}"
        
        // まずメモリキャッシュから確認
        memoryCache.get(cacheKey)?.let { return it }
        
        // ファイルキャッシュから画像を取得またはダウンロード
        val imageFile = fileCache.getOrPut(url) { tempFile ->
            try {
                downloadImage(url, tempFile)
                true
            } catch (e: Exception) {
                false
            }
        }
        
        return imageFile?.let { file ->
            // ビットマップをデコードしてメモリキャッシュに保存
            val bitmap = decodeBitmapFromFile(file, maxWidth, maxHeight)
            bitmap?.let { 
                memoryCache.put(cacheKey, it)
                it
            }
        }
    }
    
    private suspend fun downloadImage(url: String, file: File) = withContext(Dispatchers.IO) {
        URL(url).openStream().use { input ->
            FileOutputStream(file).use { output ->
                input.copyTo(output)
            }
        }
    }
    
    private fun decodeBitmapFromFile(file: File, maxWidth: Int, maxHeight: Int): Bitmap? {
        val options = BitmapFactory.Options().apply {
            inJustDecodeBounds = true
        }
        BitmapFactory.decodeFile(file.absolutePath, options)
        
        options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight)
        options.inJustDecodeBounds = false
        
        return BitmapFactory.decodeFile(file.absolutePath, options)
    }
    
    private fun calculateInSampleSize(
        options: BitmapFactory.Options,
        reqWidth: Int,
        reqHeight: Int
    ): Int {
        val height = options.outHeight
        val width = options.outWidth
        var inSampleSize = 1
        
        if (height > reqHeight || width > reqWidth) {
            val halfHeight = height / 2
            val halfWidth = width / 2
            
            while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
                inSampleSize *= 2
            }
        }
        return inSampleSize
    }
    
    fun clearCache() {
        memoryCache.clear()
        // ファイルキャッシュは自動的に管理される
    }
    
    fun getCacheStats(): String {
        return "Memory cache: ${memoryCache.size} items, " +
               "Hit ratio: ${memoryCache.hitRatio}, " +
               "File cache size: ${fileCache.size} files"
    }
}

iOS/マルチプラットフォーム対応例

// commonMain/src/commonMain/kotlin/cache/DataCacheManager.kt
expect class PlatformCache

class DataCacheManager {
    private val cache = Kache<String, String>(maxSize = 1000) {
        strategy = KacheStrategy.LRU
    }
    
    suspend fun getCachedData(key: String): String? {
        return cache.get(key)
    }
    
    suspend fun cacheData(key: String, data: String) {
        cache.put(key, data)
    }
    
    suspend fun getOrFetch(key: String, fetcher: suspend () -> String): String {
        return cache.getOrPut(key) {
            fetcher()
        }
    }
}

// androidMain/src/androidMain/kotlin/cache/PlatformCache.kt
actual class PlatformCache {
    // Android固有の実装
}

// iosMain/src/iosMain/kotlin/cache/PlatformCache.kt
actual class PlatformCache {
    // iOS固有の実装
}