KNET oferuje wielopoziomowy system cache: Memory, Disk, Persistent i Conditional (ETag).
import rip.nerd.kitsunenet.cache.KNETMemoryCache
val cache = KNETMemoryCache(
maxSize = 50, // Max 50 wpisów
defaultTtlMs = 300_000 // 5 minut TTL
)
// Użyj z interceptorem
val client = KNETClient.builder()
.addInterceptor(KNETCacheInterceptor(cache))
.build()
// Lub manualnie
cache.put(request, response)
val cached = cache.get(request)
cache.invalidate(request)
cache.clear()
import rip.nerd.kitsunenet.cache.KNETDiskCache
val cache = KNETDiskCache(
directory = context.cacheDir,
maxSizeBytes = 50 * 1024 * 1024, // 50 MB
defaultTtlMs = 3600_000 // 1 godzina
)
// Przetrwa restart aplikacji
val client = KNETClient.builder()
.addInterceptor(KNETCacheInterceptor(cache))
.build()
import rip.nerd.kitsunenet.cache.KNETPersistentCache
// Cache, który nigdy nie wygasa (do offline)
val cache = KNETPersistentCache(context)
// Zapisz
cache.store("users", usersJson)
// Pobierz
val users = cache.retrieve("users")
// Z timestamp
val (data, timestamp) = cache.retrieveWithTimestamp("users")
if (System.currentTimeMillis() - timestamp > 3600_000) {
// Dane starsze niż 1h - odśwież
}
import rip.nerd.kitsunenet.cache.KNETConditionalCache
val cache = KNETConditionalCache()
// Automatycznie używa If-None-Match / If-Modified-Since
val response = cache.getWithValidation(client, request)
// Jeśli serwer zwróci 304 Not Modified,
// zwraca cached response bez pobierania body
// Zapisuje ETag i Last-Modified automatycznie
val interceptor = KNETCacheInterceptor(
cache = cache,
defaultTtlMs = 300_000,
cacheOnlyGet = true, // Cache tylko GET
respectCacheHeaders = true, // Respektuj Cache-Control
staleWhileRevalidate = true // Zwróć stale, odśwież w tle
)
// Cache-Control headers obsługiwane:
// - max-age=300
// - no-cache
// - no-store
// - must-revalidate
// - private / public
enum class CacheStrategy {
CACHE_FIRST, // Cache → Network (fallback)
NETWORK_FIRST, // Network → Cache (fallback)
CACHE_ONLY, // Tylko cache
NETWORK_ONLY, // Tylko network
STALE_WHILE_REVALIDATE // Cache + odśwież w tle
}
val client = KNETClient.builder()
.cacheStrategy(CacheStrategy.STALE_WHILE_REVALIDATE)
.build()
// Lub per request
val request = KNETRequest.get(url)
.withCacheStrategy(CacheStrategy.NETWORK_FIRST)
val cache = KNETMemoryCache(
keyGenerator = { request ->
// Domyślnie: method + url
// Custom: uwzględnij headers
buildString {
append(request.method)
append(":")
append(request.url)
request.headers["Accept-Language"]?.let {
append(":lang=$it")
}
}
}
)
// Pojedynczy wpis
cache.invalidate(request)
// Po URL pattern
cache.invalidateByPattern("https://api.example.com/users.*")
// Po tagu
cache.invalidateByTag("users")
// Wszystko
cache.clear()
// Wygasłe wpisy
val removed = cache.clearExpired()
println("Usunięto $removed wygasłych wpisów")
// Pre-load cache
suspend fun warmCache() {
val urls = listOf(
"/api/config",
"/api/categories",
"/api/featured"
)
urls.forEach { path ->
try {
val response = client.get("$baseUrl$path")
cache.put(KNETRequest.get("$baseUrl$path"), response)
} catch (e: Exception) {
Log.w("Cache", "Failed to warm: $path")
}
}
}
val stats = cache.getStats()
println("Entries: ${stats.size}")
println("Hits: ${stats.hits}")
println("Misses: ${stats.misses}")
println("Hit rate: ${stats.hitRate}%")
println("Memory used: ${stats.memorySizeBytes / 1024} KB")
// Monitorowanie
cache.onHit { request ->
analytics.log("cache_hit", request.url)
}
cache.onMiss { request ->
analytics.log("cache_miss", request.url)
}
class OfflineFirstRepository(
private val client: KNETClient,
private val cache: KNETPersistentCache,
private val networkChecker: NetworkChecker
) {
suspend fun getUsers(): List<User> {
// 1. Sprawdź cache
val cached = cache.retrieve("users")
// 2. Jeśli offline, zwróć cache
if (!networkChecker.isOnline()) {
return cached?.let { parseUsers(it) } ?: emptyList()
}
// 3. Pobierz z sieci
return try {
val response = client.get("$baseUrl/users")
val users = response.jsonList<User>()
// 4. Zapisz do cache
cache.store("users", response.bodyString)
users
} catch (e: Exception) {
// 5. Fallback do cache
cached?.let { parseUsers(it) } ?: throw e
}
}
}