Kache
キャッシュライブラリ
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固有の実装
}