Эффективное кэширование необходимо для мобильных приложений для обеспечения быстрого, отзывчивого опыта при минимизации использования сети и потребления батареи. В сочетании с правильным тестированием производительности API, стратегии кэширования формируют основу оптимизации производительности мобильных приложений.
Android Стратегии Кэширования
HTTP Кэш с OkHttp
val cacheSize = 10 * 1024 * 1024 // 10 MB
val cache = Cache(context.cacheDir, cacheSize.toLong())
val client = OkHttpClient.Builder()
.cache(cache)
.build()
Пользовательская Реализация Кэша
class ApiCache(private val context: Context) {
private val prefs = context.getSharedPreferences("api_cache", Context.MODE_PRIVATE)
fun <T> getCached(key: String, type: Class<T>, maxAge: Long = 3600_000): T? {
val cachedJson = prefs.getString(key, null) ?: return null
val timestamp = prefs.getLong("${key}_timestamp", 0)
if (System.currentTimeMillis() - timestamp > maxAge) {
return null
}
return Gson().fromJson(cachedJson, type)
}
fun <T> cache(key: String, data: T) {
val json = Gson().toJson(data)
prefs.edit()
.putString(key, json)
.putLong("${key}_timestamp", System.currentTimeMillis())
.apply()
}
}
Тестирование Стратегий Кэша
Всестороннее тестирование поведения кэша критически важно. Для более широких техник API тестирования, изучите лучшие практики мастерства API тестирования, которые дополняют валидацию кэша.
@Test
fun testCacheHit() = runTest {
val repository = UserRepository(mockApi, cache)
// Первый вызов - должен вызвать API
repository.getUser("123")
verify(exactly = 1) { mockApi.getUser("123") }
// Второй вызов - должен использовать кэш
repository.getUser("123")
verify(exactly = 1) { mockApi.getUser("123") }
}
@Test
fun testCacheExpiration() = runTest {
repository.getUser("123")
delay(3600_001) // Ждём истечения
repository.getUser("123")
verify(exactly = 2) { mockApi.getUser("123") }
}
@Test
fun testOfflineCache() = runTest {
networkMonitor.setConnected(true)
repository.getUser("123")
networkMonitor.setConnected(false)
every { mockApi.getUser(any()) } throws UnknownHostException()
val cachedUser = repository.getUser("123")
assertNotNull(cachedUser)
}
Тестирование offline сценариев критически важно для мобильных приложений. Узнайте больше о всесторонних стратегиях тестирования сетевых условий, чтобы гарантировать надёжную работу вашего кэша во всех состояниях подключения.
Политики Кэша
enum class CachePolicy(val maxAge: Long) {
SHORT(5 * 60 * 1000), // 5 минут
MEDIUM(30 * 60 * 1000), // 30 минут
LONG(24 * 60 * 60 * 1000), // 24 часа
PERMANENT(Long.MAX_VALUE)
}
class CacheStrategy {
// Network-first
suspend fun <T> networkFirst(key: String, fetch: suspend () -> T): T {
return try {
val result = fetch()
cache.cache(key, result)
result
} catch (e: IOException) {
cache.getCached(key) ?: throw e
}
}
// Cache-first
suspend fun <T> cacheFirst(key: String, fetch: suspend () -> T): T {
cache.getCached(key)?.let { cached ->
CoroutineScope(Dispatchers.IO).launch {
try {
cache.cache(key, fetch())
} catch (e: Exception) { }
}
return cached
}
val result = fetch()
cache.cache(key, result)
return result
}
}
Синхронизация Кэша
class SyncManager(private val api: ApiService, private val database: AppDatabase) {
suspend fun syncUsers() {
val users = api.getUsers()
database.withTransaction {
database.userDao().deleteAll()
database.userDao().insertAll(users.map { it.toEntity() })
setSyncTimestamp("users", System.currentTimeMillis())
}
}
fun needsSync(key: String, maxAge: Long = 3600_000): Boolean {
val lastSync = getSyncTimestamp(key)
return System.currentTimeMillis() - lastSync > maxAge
}
}
Управление Хранилищем
class CacheSizeManager(private val maxSize: Long = 50 * 1024 * 1024) {
fun enforceLimit() {
val cacheDir = context.cacheDir
val currentSize = calculateSize(cacheDir)
if (currentSize > maxSize) {
val filesToDelete = getOldestFiles(cacheDir, currentSize - maxSize)
filesToDelete.forEach { it.delete() }
}
}
}
Лучшие Практики
- Используйте подходящие длительности кэша - Короткие для динамических данных, длинные для статических
- Реализуйте инвалидацию кэша - Очищайте кэш при logout, обновлениях
- Мониторьте размер кэша - Предотвращайте неограниченный рост
- Тестируйте offline сценарии - Обеспечьте graceful деградацию
- Используйте ETags и условные запросы - Оптимизируйте bandwidth
Заключение
Эффективное API кэширование требует:
- Стратегических политик кэша
- Правильных механизмов инвалидации
- Управления хранилищем
- Offline поддержки
- Всестороннего тестирования
Хорошо реализованное кэширование значительно улучшает производительность мобильных приложений, снижает затраты и улучшает пользовательский опыт во всех сетевых условиях.