El caching efectivo es esencial para aplicaciones móviles para proporcionar experiencias rápidas y responsivas mientras se minimiza el uso de red y consumo de batería. Combinado con pruebas de rendimiento API adecuadas, las estrategias de caching forman la base de la optimización de rendimiento de apps móviles.
Si estás desarrollando APIs para móviles, considera usar mock servers durante el desarrollo para probar comportamientos de caché sin dependencias de backend. Entender estrategias de versionado de API también es crucial para mantener compatibilidad de caché entre versiones de la aplicación.
Android Estrategias de Caching
HTTP Cache con OkHttp
val cacheSize = 10 * 1024 * 1024 // 10 MB
val cache = Cache(context.cacheDir, cacheSize.toLong())
val client = OkHttpClient.Builder()
.cache(cache)
.build()
Implementación de Caché Personalizado
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()
}
}
Testing Estrategias de Caché
El testing comprensivo del comportamiento de caché es esencial. Para técnicas más amplias de testing API, explora mejores prácticas de dominio de testing API que complementan la validación de caché.
@Test
fun testCacheHit() = runTest {
val repository = UserRepository(mockApi, cache)
// Primera llamada - debe llamar API
repository.getUser("123")
verify(exactly = 1) { mockApi.getUser("123") }
// Segunda llamada - debe usar caché
repository.getUser("123")
verify(exactly = 1) { mockApi.getUser("123") }
}
@Test
fun testCacheExpiration() = runTest {
repository.getUser("123")
delay(3600_001) // Esperar expiración
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)
}
Probar escenarios offline es crítico para apps móviles. Aprende más sobre estrategias comprensivas de testing de condiciones de red para asegurar que tu caché funcione confiablemente en todos los estados de conectividad.
Políticas de Caché
enum class CachePolicy(val maxAge: Long) {
SHORT(5 * 60 * 1000), // 5 minutos
MEDIUM(30 * 60 * 1000), // 30 minutos
LONG(24 * 60 * 60 * 1000), // 24 horas
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
}
}
Sincronización de Caché
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
}
}
Gestión de Almacenamiento
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() }
}
}
}
Mejores Prácticas
- Usa duraciones de caché apropiadas - Corto para datos dinámicos, largo para estáticos
- Implementa invalidación de caché - Limpia caché en logout, actualizaciones
- Monitorea tamaño de caché - Previene crecimiento ilimitado
- Prueba escenarios offline - Asegura degradación graceful
- Usa ETags y requests condicionales - Optimiza ancho de banda
Conclusión
El caching efectivo de API requiere:
- Políticas de caché estratégicas
- Mecanismos de invalidación apropiados
- Gestión de almacenamiento
- Soporte offline
- Testing comprensivo
El caching bien implementado mejora significativamente el rendimiento de apps móviles, reduce costos y mejora la experiencia de usuario en todas las condiciones de red.
Ver También
- Pruebas de Rendimiento API - Técnicas para validar rendimiento de endpoints
- Optimización de Rendimiento de Apps Móviles - Estrategias integrales de optimización
- Mock Servers para Desarrollo Móvil - Simular APIs durante desarrollo
- Versionado de API para Móviles - Gestionar compatibilidad entre versiones
- Testing Móvil 2025: iOS, Android y Más Allá - Tendencias actuales en testing móvil