estrategia de caching sprint 3 - jcrinconv/MISW4203-2026-12-ing-sw-apps-moviles GitHub Wiki
Las estrategias implementadas en el Sprint 2 se mantienen como base del sistema de cachΓ© de la aplicaciΓ³n:
| Estrategia | Recurso optimizado | Mecanismo |
|---|---|---|
| CacheManager | Red, latencia, baterΓa | Singleton en memoria con Cache-Aside |
| Glide | Red, memoria con imΓ‘genes, UX | LRU automΓ‘tico (memoria + disco) |
El patrΓ³n Cache-Aside sigue operando en todos los repositorios existentes:
| Repositorio | MΓ©todo | Dato cacheado |
|---|---|---|
AlbumRepositoryImpl |
getAlbums() |
Lista de Γ‘lbumes |
AlbumRepositoryImpl |
getAlbumById(id) |
Detalle de Γ‘lbum por ID |
MusicianRepositoryImpl |
getMusicians() |
Lista de mΓΊsicos |
MusicianRepositoryImpl |
getMusicianById(id) |
Detalle de mΓΊsico por ID |
MusicianRepositoryImpl |
getPerformerPrizesByMusicianId(id) |
Premios por mΓΊsico |
CollectorRepositoryImpl |
getCollectors() |
Lista de coleccionistas |
Caso optimizado: En el Sprint 2, CacheManager requerΓa un Context como parΓ‘metro en cada llamada a getInstance(context), acoplando el sistema de cachΓ© a la capa de Android y dificultando su uso en capas de datos puras.
OptimizaciΓ³n implementada: CacheManager fue refactorizado para ser independiente del contexto. Todas las llamadas pasaron de:
// Sprint 2
CacheManager.getInstance(context).getAlbums()
CacheManager.getInstance(context).addAlbums(albums)a:
// Sprint 3
CacheManager.getInstance().getAlbums()
CacheManager.getInstance().addAlbums()Beneficio: El CacheManager ahora puede ser consumido desde cualquier capa sin necesidad de propagar el Context, simplificando los repositorios y reduciendo el acoplamiento arquitectural.
La consulta del detalle de un coleccionista fue una funcionalidad aΓ±adida en este sprint. Siguiendo el patrΓ³n establecido por las micro-optimizaciones, el detalle de un coleccionista tambiΓ©n es guardado en cachΓ©:
override suspend fun getCollectorById(id: Int): Collector {
val potentialResp = CacheManager.getInstance().getCollectorDetail(id)
return if (potentialResp == null) {
Log.d("Cache decision", "get collector $id from network")
val collector = remoteDataSource.fetchCollectorById(id)
CacheManager.getInstance().addCollectorDetail(id, collector)
collector
} else {
Log.d("Cache decision", "return collector $id from cache")
potentialResp
}
}Beneficio observable: La segunda apertura del detalle de un coleccionista resuelve su informaciΓ³n en memoria (~ms) sin llamadas a red, reduciendo la latencia percibida y el consumo de datos.
Para el formulario de creaciΓ³n de un Γ‘lbum, como existe la posibilidad de obtener los gΓ©neros y sellos discogrΓ‘ficos de manera remota, se incorporΓ³ cachΓ© para almacenar sus datos. Ejemplo:
override suspend fun getGenres(): List<String> {
val potentialResp = CacheManager.getInstance().getGenres()
return if (potentialResp.isEmpty()) {
Log.d("Cache decision", "get genres from network")
val genres = remoteDataSource.fetchGenres()
CacheManager.getInstance().addGenres(genres)
genres
} else {
Log.d("Cache decision", "return ${potentialResp.size} genres from cache")
potentialResp
}
}Beneficio observable: La segunda apertura del formulario de creaciΓ³n resuleve gΓ©neros y sellos en memoria (~ms) sin llamadas a red, reduciendo la latencia percibida y el consumo de datos.
Caso optimizado: En el Sprint 2, el cachΓ© no se invalidaba al crear o modificar datos, lo que podΓa ocasionar que la app mostrar informaciΓ³n obsoleta tras una operaciΓ³n de escritura exitosa.
OptimizaciΓ³n implementada: Se incorporΓ³ invalidaciΓ³n selectiva del cachΓ© en las operaciones de mutaciΓ³n de AlbumRepositoryImpl:
| OperaciΓ³n | CachΓ© invalidado | Motivo |
|---|---|---|
createAlbum() |
clearAlbums() |
La lista de Γ‘lbumes cambiΓ³, debe refrescarse |
addTrack() |
clearAlbumDetail() |
El detalle del Γ‘lbum cambiΓ³, debe refrescarse |
override suspend fun createAlbum(album: AlbumRequest): Album {
val result = remoteDataSource.createAlbum(album)
// Clear albums cache to force refresh
CacheManager.getInstance().clearAlbums()
return result
}
override suspend fun addTrack(albumId: Int, track: TrackRequest): Track {
val result = remoteDataSource.addTrack(albumId, track)
// Clear album detail cache to force refresh
CacheManager.getInstance().clearAlbumDetail(albumId)
return result
}Beneficio: Garantiza consistencia entre los datos mostrados en UI y el estado real del backend despuΓ©s de cualquier operaciΓ³n de escritura, evitando que el usuario vea datos obsoletos.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β UI Layer β
β AlbumListFragment Β· AlbumDetailFragment β
β AlbumCreateFragment Β· TrackAssociateFragment β
β MusicianListFragment Β· MusicianDetailFragment β
β CollectorListFragment β
βββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β observa LiveData
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ViewModels β
β AlbumViewModel Β· AlbumDetailViewModel β
β AlbumCreateViewModel Β· TrackAssociateViewModel β
β MusicianViewModel Β· MusicianDetailViewModel β
β CollectorViewModel β
βββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β suspend fun
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Repository Layer (lΓ³gica Cache-Aside + InvalidaciΓ³n) β
β AlbumRepositoryImpl β
β MusicianRepositoryImpl β
β CollectorRepositoryImpl β
ββββββββββββ¬βββββββββββββββββββββββββββ¬βββββββββββββββββββββ
β β
Cache Hit Cache Miss / Escritura
β β
βΌ βΌ
βββββββββββββββββββββββ βββββββββββββββββββββββββββ
β CacheManager β β RemoteDataSource β
β (Singleton) β β β Retrofit + OkHttp3 β
β sin Context β NUEVO β β β Backend API β
β β ββββββββββββββ¬βββββββββββββ
β albums: List β β
β musicians: List ββββββββββββββββββ
β collectors: List β guarda en cachΓ©
β albumDetails: Map β
β musicianDetails: Mapβ
β performerPrizes: Mapβ
β genres: List β NUEVO Sprint 3
β recordLabels: List β NUEVO Sprint 3
β β
β clearAlbums() β NUEVO: invalidaciΓ³n
β clearAlbumDetail()β NUEVO: invalidaciΓ³n
βββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Glide (paralelo, en UI Layer) β
β β
β ImageView.load(url) β
β β β
β Memory LRU β Disk Cache β Network (OkHttp3) β
β β β β β
β instant ~10-50ms ~500-1500ms β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Pantalla | CachΓ© de datos | CachΓ© de imΓ‘genes | Beneficio observable |
|---|---|---|---|
| Lista de Γ‘lbumes |
getAlbums() β lista en memoria |
Portadas via Glide | Segunda visita instantΓ‘nea, scroll fluido |
| Detalle de Γ‘lbum |
getAlbumDetail(id) β mapa por ID |
Portada grande via Glide | Revisitar mismo Γ‘lbum sin espera |
| Crear Γ‘lbum |
getGenres() + getRecordLabels() β listas en memoria |
- | Formulario abre sin llamadas a red en segunda apertura |
clearAlbums() tras Γ©xito |
- | Listado de Γ‘lbumes siempre refleja el Γ‘lbum aΓ±adido | |
| Asociar canciΓ³n |
clearAlbumDetail() tras Γ©xito |
- | Detalle del Γ‘lbum siempre refleja tracks actualizados |
| Detalle de mΓΊsico |
getMusicianById(id) + getPerformerPrizes(id)
|
Foto via Glide | Premios y datos cargados al instante |
| Lista de coleccionistas |
getCollectors() β lista en memoria |
- | Datos disponibles sin red |
| Detalle de coleccionista |
getCollectorById(id) β mapa por ID |
Fotos de artistas y portadas de Γ‘lbumes via Glide | Revisitar mismo coleccionista sin espera |
Referencia: Perfilamiento y resultados Sprint 3.xlsx
Las estrategias de caching implementadas se correlacionan directamente con cambios observables en las mΓ©tricas de memoria y concurrencia obtenidas durante el perfilamiento:
-
GestiΓ³n de hilos y concurrencia: El uso de CacheManager, Glide y caching de servicios Retrofit reduce operaciones repetitivas de red y recreaciones innecesarias de objetos, contribuyendo a estabilizar parcialmente la cantidad de hilos en varias HUs, especialmente en Android 13. En Android 15 y 16 el comportamiento es mΓ‘s variable debido a mecanismos internos de scheduling y administraciΓ³n de tareas.
-
Consumo de memoria: La reutilizaciΓ³n de datos cacheados y de objetos pesados como SimpleDateFormat ayuda a disminuir recreaciones innecesarias en memoria. Sin embargo, optimizaciones como RecyclerView y DiffUtil requieren estructuras auxiliares para reciclar vistas y calcular diferencias, por lo que algunas HUs presentan incrementos moderados de RAM, particularmente en Android 15 y 16.
-
Renderizado y trabajo de UI: La reutilizaciΓ³n de datos en cachΓ© reduce recargas completas de pantallas y solicitudes HTTP redundantes. Adicionalmente, optimizaciones como la reducciΓ³n de overdraw, el caching de instancias Retrofit y la migraciΓ³n hacia RecyclerView disminuyen trabajo redundante de UI y recreaciΓ³n innecesaria de layouts.
Las estrategias de caching y micro-optimizaciones actΓΊan conjuntamente como mecanismos de reducciΓ³n de trabajo redundante, intercambiando un pequeΓ±o costo adicional de memoria auxiliar por ahorro en tiempo de respuesta, reutilizaciΓ³n de recursos y menor cantidad de operaciones repetitivas de UI y red.
En el Sprint 3 se extiende la estrategia de caching del Sprint 2 en tres dimensiones:
-
RefactorizaciΓ³n estructural:
CacheManagereliminΓ³ su dependencia deContext, mejorando la arquitectura y testeabilidad. - Cobertura de nuevos datos: El detalle de un coleccionista, los gΓ©neros y los sellos discogrΓ‘ficos se cachean, completando la cobertura para todos los flujos de lectura incluyendo el de creaciΓ³n.
- Consistencia tras escritura: La invalidaciΓ³n selectiva de cachΓ© garantiza que los datos mostrados en UI reflejen siempre el estado real del backend.
| Estrategia | Recurso optimizado | Mecanismo |
|---|---|---|
| CacheManager | Red, latencia, baterΓa | Singleton en memoria con Cache-Aside + InvalidaciΓ³n |
| Glide | Red, memoria de imΓ‘genes, UX | LRU automΓ‘tico (memoria + disco) |
| Micro-optimizaciones | CPU, GPU, memoria | Caching de instancias, reducciΓ³n de jerarquΓa, limpieza de recursos |
TecnologΓas: Glide 5.0.7 Β· OkHttp3 5.3.2 Β· Retrofit2 3.0.0 Β· Kotlin Coroutines