Domina la descripción del puesto
Guía de Estudio
Cada requisito que un rol senior de Android comúnmente pide — explicado a profundidad senior, y luego ligado a cómo lo aplicarías en un equipo real. Lee el concepto, luego la línea “en la práctica” para que puedas hablar desde la experiencia.
01 · Fundamentos del lenguaje Kotlin
Lo que buscan: Kotlin idiomático, de producción — no Java escrito en sintaxis de Kotlin. Fluidez con val vs var, inmutabilidad por defecto, seguridad de nulos (?., ?:, !! como mala práctica), data classes, jerarquías sealed para modelar estado, y las funciones de ámbito (let, run, with, apply, also).
- Prefiere
valy colecciones inmutables; expónList, almacenaMutableListprivadamente. - Modela estado exhaustivo con
sealed interface+when(sin ramaelse→ el compilador impone completitud). - Delegación:
by lazy,by viewModels(), delegados de propiedades, y delegación de clases conby. inline+reifiedpara conservar info de tipo genérico en tiempo de ejecución; entiende el costo (tamaño de código) de inlining.
!! en una revisión es una señal de alarma — convierte una garantía en tiempo de compilación en un crash en tiempo de ejecución.02 · Coroutines y concurrencia estructurada
Lo que buscan: coroutines profundas — no solo launch { }. La concurrencia estructurada significa que cada coroutine tiene un CoroutineScope padre; cancelar el scope cancela sus hijos, y un fallo hijo cancela hermanos (a menos que un SupervisorJob los aísle).
- Las funciones suspend deben ser seguras para el principal: pueden llamarse en el hilo principal e internamente cambiar con
withContext(Dispatchers.IO). - Dispatchers:
Main(UI),IO(I/O bloqueante, pool grande),Default(trabajo de CPU),Unconfined(rara vez). - La cancelación es cooperativa: verifica
isActive/ llamaensureActive(), y nunca traguesCancellationException. coroutineScopevssupervisorScope;async/awaitpara descomposición paralela.
viewModelScope y lifecycleScope son scopes estructurados conectados a un ciclo de vida — se cancelan automáticamente, lo cual es por qué casi nunca necesitas GlobalScope (que filtra porque nada lo cancela).viewModelScope se cancela cuando el ViewModel se limpia, así que un cambio de configuración o retroceso no filtra una solicitud ni actualiza una UI muerta.03 · Flow, StateFlow y SharedFlow
Lo que buscan: streams reactivos bien hechos. Flow es un stream asíncrono frío; los operadores (map, filter, combine, flatMapLatest) se ejecutan en el contexto del collector a menos que uses flowOn.
| StateFlow | SharedFlow | |
|---|---|---|
| Mantiene valor | Siempre (requiere inicial) | Replay opcional |
| Conflación | Conflado, distinto-hasta-que-cambie | Buffer configurable |
| Usar para | Estado observable de UI | Eventos únicos (navegar, toast) |
- Recolecta con
repeatOnLifecycle(STARTED)(ocollectAsStateWithLifecycle()en Compose) para que la recolección se detenga en segundo plano — nolaunchWhenStarted(obsoleto, mantiene el upstream caliente). stateIn(scope, SharingStarted.WhileSubscribed(5000), initial)comparte un flow frío como estado y detiene el upstream 5s después del último suscriptor — sobrevive rotación sin re-obtener.
onCreate con un launch simple mantiene el collector vivo en segundo plano, desperdiciando trabajo y arriesgando actualizaciones de UI obsoletas. repeatOnLifecycle es la solución que los entrevistadores quieren escuchar.04 · Jetpack Compose — composición y recomposición
Lo que buscan: el modelo mental. Un @Composable es una función que describe UI a partir del estado; Compose construye un árbol y recomposa (reinvoca) solo las funciones cuyas entradas cambiaron. No hay jerarquía de vistas que mutar — cambias el estado y el framework reconcilia.
- Ámbito de recomposición: el composable contenedor más pequeño que lee un estado dado. Leer estado bajo en el árbol mantiene la recomposición local.
- Estabilidad: Compose omite un composable si sus parámetros son
@Stable/@Immutablee inmutables. Tipos inestables (ej.Listde una fuente no estable) impiden el skip → usaImmutableList/kotlinx.collections.immutable. - Fases: Composición → Layout → Dibujo. Difiere lecturas a la fase más tardía (ej. lambda
Modifier.offset { }) para omitir recomposición en scroll/animación.
remember.05 · Estado y efectos secundarios en Compose
Lo que buscan: state hoisting y las APIs de efectos secundarios. remember cachea entre recomposiciones; rememberSaveable sobrevive cambio de configuración/muerte del proceso. Eleva el estado hacia arriba (composables sin estado toman value + onValueChange) para que la UI sea reutilizable y testeable.
LaunchedEffect(key)— ejecuta un bloque suspend vinculado a la composición; se reinicia cuandokeycambia, se cancela al salir.rememberCoroutineScope()— lanza desde callbacks (ej. clic en botón), con scope de la composición.DisposableEffect— registra/desregistra (listeners, observers) con unonDispose.derivedStateOf— calcula desde otro estado y solo recomposa cuando el resultado cambia (ej.showButton = scrollOffset > 0).produceState— conecta una fuente asíncrona no-Compose a unState;snapshotFlow— convierte estado de Compose en un Flow.
LaunchedEffect — se dispara en cada recomposición. Decir "los efectos pertenecen a manejadores de efectos con key correcta" señala madurez real de Compose.06 · Ciclo de vida de Activity / Fragment y cambios de configuración
Lo que buscan: el ciclo de vida al dedillo. Activity: onCreate → onStart → onResume (primer plano) → onPause → onStop → onDestroy. Un cambio de configuración (rotación, modo oscuro, idioma, escala de fuente) destruye y recrea la Activity por defecto.
- El ciclo de vida de la vista del Fragment es separado del fragment: usa
viewLifecycleOwnerpara observadores, o filtras cuando la vista se destruye pero el fragment no. - No pelees contra cambios de configuración con
android:configChangesa menos que realmente controles el redibujado — prefiere sobrevivir estado en un ViewModel. onSaveInstanceState(Bundle)persiste estado pequeño de UI a través de recreación y muerte del proceso (parcelable, < ~1MB).
07 · ViewModel, SavedStateHandle y muerte del proceso
Lo que buscan: dónde vive el estado y cómo sobrevive. Un ViewModel sobrevive cambios de configuración (con scope al ViewModelStoreOwner) pero se limpia cuando el propietario realmente termina. No sobrevive muerte del proceso por sí solo — el OS puede matar una app en segundo plano para recuperar memoria.
SavedStateHandlees el puente: un mapa de valores respaldado por estado de instancia guardado, así que los valores sobreviven muerte del proceso. Inyectalo en el ViewModel y expón estado víagetStateFlow(key, default).- Nunca mantengas una referencia
Context/View/Activity en un ViewModel (filtra). Usa el contexto de aplicación deAndroidViewModelsolo si es inevitable. - Prueba la ruta de restauración: mata el proceso desde herramientas de desarrollo ("No conservar actividades") y confirma que la pantalla se reconstruye correctamente.
08 · Arquitectura — MVVM, MVI y Clean Architecture
Lo que buscan: una arquitectura defendible con flujo de datos unidireccional. MVVM: el ViewModel expone estado observable; la Vista lo renderiza y envía eventos hacia arriba. MVI condensa esto en un único UiState inmutable + un stream de Intent/Action reducido a nuevo estado — genial para pantallas complejas y con muchos experimentos.
- Capas de Clean Architecture: UI → Dominio (casos de uso, puro Kotlin, sin Android) → Datos (repositorios, fuentes). Las dependencias apuntan hacia adentro; el dominio no sabe nada de Retrofit o Room.
- Fuente única de verdad: el repository decide caché vs red; la UI nunca llama a la API directamente.
- Modela el estado como un solo objeto:
data class UiState(val items: List, val loading: Boolean, val error: String?)— renderiza todo, evita booleanos dispersos.
09 · Inyección de dependencias — Hilt y Dagger
Lo que buscan: DI para mantener el código testeable y desacoplado. Hilt se apoya en Dagger y genera componentes vinculados a ciclos de vida de Android: @HiltAndroidApp, @AndroidEntryPoint, @HiltViewModel, y scopes (@Singleton, @ActivityRetainedScoped, @ViewModelScoped).
@Module+@Providespara cosas que no posees (Retrofit, OkHttp);@Bindspara interfaz→impl.- Inyección por constructor (
@Inject constructor) donde puedas — es la más testeable. - Qualifiers (
@Named/ custom) desambigúan múltiples bindings del mismo tipo (ej. dos clientes OkHttp).
10 · Persistencia — Room y DataStore
Lo que buscan: almacenamiento local elegido correctamente. Room es el ORM de SQLite: @Entity, @Dao, @Query, con verificación SQL en tiempo de compilación y tipos de retorno Flow para lecturas reactivas. DataStore reemplaza SharedPreferences: Preferences DataStore (clave-valor) o Proto DataStore (tipado), ambos asíncronos y transaccionales.
- Los DAOs de Room que retornan
Flow<List<T>>emiten en cada cambio — la columna vertebral de offline-first. - Migraciones: proporciona un
Migrationo arriesga un crash en cambio de esquema; exporta el esquema para tests. - Nunca hagas disco I/O en el hilo principal — los DAOs
suspendde Room y DataStore son seguros para el principal por diseño.
apply() difiere la escritura a disco pero el commit en memoria aún bloquea). DataStore corrige eso con un Flow + coroutines." Ese contraste es una pregunta senior frecuente.11 · Red — Retrofit, OkHttp y serialización
Lo que buscan: una capa de red robusta. Retrofit define una interfaz de API tipada; OkHttp es el motor con el pool de conexiones, caché e interceptores. Serializa con Moshi o kotlinx.serialization.
- Interceptores: uno de aplicación para headers de auth/logging; uno de red para control de caché. Añade un
Authenticatorde refresh de token para 401s. - Modela resultados con un tipo
sealedResult/Either— nunca dejes que excepciones crudas lleguen a la UI. - Mapea DTOs → modelos de dominio en el repository; los DTOs nunca se filtran a Compose.
- Caché HTTP de OkHttp + ETags para GETs condicionales; reintenta solo llamadas idempotentes con backoff.
12 · Navegación y deep linking
Núcleo: Navigation-Compose modela la app como un grafo de destinos con rutas tipadas (rutas type-safe vía objetos de ruta @Serializable en Navigation 2.8+). El NavController posee el back stack; pasas argumentos y haces pop con resultados.
- Eleva el
NavControlleren la raíz del grafo; pasa lambdas (onNavigateToDetail) a pantallas para que los composables se mantengan desacoplados de navegación y testeables. - Deep links: declara
<intent-filter>+android:autoVerifypara App Links (https verificado) y unnavDeepLinkque mapea la URI a un destino. - Gráficos anidados para módulos de features;
SavedStateHandlerecibe args de navegación dentro del ViewModel.
assetlinks.json en tu dominio — un paso que la gente olvida.13 · Trabajo en segundo plano — WorkManager, Services y broadcasts
Lo que buscan: la herramienta correcta para trabajo fuera de pantalla. WorkManager es el predeterminado para trabajo diferido, garantizado (sincronización, subida) — sobrevive muerte del proceso y reinicio, respeta restricciones (red, carga), y retrocede en fallo.
OneTimeWorkRequestvsPeriodicWorkRequest(intervalo mínimo de 15 min); encadena conbeginWith().then(); trabajo único para deduplicar.- Services de primer plano para trabajo visible continuo por el usuario (reproducción, navegación) — requieren una notificación y, en Android 14+, un
foregroundServiceTypedeclarado. - Límites modernos de segundo plano:
JobScheduler/WorkManager sobre wake-locks implícitos;BroadcastReceiverpara eventos del sistema, pero la mayoría de broadcasts implícitos están restringidos.
14 · Rendimiento — jank, timing de frames y baseline profiles
Núcleo: la métrica es frames renderizados dentro del presupuesto de 16.6ms (60fps; 8.3ms a 120Hz). Jank = un frame perdido, surfaced por JankStats, la librería Macrobenchmark, y trazas de Perfetto.
- Baseline Profiles: distribuye pistas de compilación AOT para rutas calientes para que las primeras ejecuciones no se interpreten — victorias mayores en arranque en frío y scroll, generados vía Macrobenchmark.
- Compose: evita parámetros inestables, difiere lecturas de estado a layout/draw, usa keys de
LazyColumn, yremembercomputaciones costosas. - Disciplina del hilo principal: sin I/O, sin parseo JSON grande, sin decodificación de bitmap en el hilo de UI.
15 · Memory leaks, ANRs y profiling
Núcleo: dos modos de fallo. Los leaks retienen objetos que el GC debería liberar — el leak clásico de Android es un Context/View retenido más allá de su ciclo de vida (refs estáticos, inner classes, callbacks, un Handler con runnables retrasados). Los ANRs se disparan cuando el hilo principal está bloqueado > 5s (despacho de entrada) — casi siempre I/O en el hilo principal o contención de locks.
- LeakCanary en debug detecta automáticamente instancias retenidas y vuelca la cadena de referencias — nómbralo como tu primera herramienta.
- Memory Profiler de Android Studio + heap dumps para crecimiento acumulativo; trazas de ANR en Play Console /
/data/anr/traces.txt. - Leak de coroutine común: lanzar en
GlobalScopeo recolectar un flow sin conciencia del ciclo de vida.
16 · Arranque de app y R8 / ProGuard
Núcleo: arranque en frío = fork del proceso → Application.onCreate → primer frame. El presupuesto se gasta en inicialización ansiosa, grafos de dependencias grandes, y trabajo en el hilo principal.
- Usa la librería App Startup para consolidar y ordenar inicializadores (e inicializar lazy los que no se necesitan al lanzamiento).
- R8 reduce, optimiza, y ofusca: eliminación de código muerto/recursos corta tamaño y mejora carga; las reglas de keep (
-keep) protegen reflexión (Gson, serialización). - Difiere trabajo no crítico fuera de la ruta de lanzamiento; muestra contenido rápido y hidrata el resto.
Application.onCreate, añado un baseline profile, y mantengo R8 habilitado." Tratar el arranque como un presupuesto defendido es una señal senior.17 · Testing — JUnit, MockK, Turbine y tests de UI Compose
Lo que buscan: una pirámide de testing real. Muchos tests unitarios rápidos (JUnit + MockK) sobre ViewModels, casos de uso y mappers; menos tests de integración; algunos UI/E2E (Compose test o Espresso) en flujos críticos.
- Coroutines/Flow:
runTest+ unTestDispatcher(inyecta el dispatcher, nunca hardcodeDispatchers.IO); aserta emisiones con Turbine. - Fakes vs mocks: prefiere un repository fake escrito a mano para tests basados en estado; usa mocks para verificación de interacciones. El exceso de mocking acopla tests a la implementación.
- Robolectric ejecuta el framework de Android en la JVM (rápido, sin dispositivo); el Compose UI test usa semántica (
onNodeWithText,assertIsDisplayed).
class Repo(private val io: CoroutineDispatcher) para que los tests pasen StandardTestDispatcher() y controlen el tiempo virtual. Los dispatchers hardcodeados hacen los tests inestables.18 · Gradle — variantes, catálogos de versiones y modularización
Lo que buscan: puedes razonar sobre la compilación. Variantes de build = tipos de build (debug/release) × product flavors (free/paid, staging/prod). Los catálogos de versiones (libs.versions.toml) centralizan versiones de dependencias.
- KSP vs kapt: KSP es el procesador de anotaciones nativo de Kotlin — mucho más rápido que kapt (que genera stubs de Java). Room/Hilt/Moshi soportan KSP; migra de kapt para velocidad de build.
- Modularización: divide por feature (
:feature:home) y capa (:core:data) para compilaciones paralelas, compilación incremental más rápida, y límites forzados. - Convention plugins (
build-logic) eliminan duplicación en configuración de módulos;apivsimplementationcontrola qué se filtra transitivamente.
:feature físicamente no puede importar los internos de otra feature." Nombrar velocidad de build y límites es la respuesta de staff.19 · Seguridad — Keystore, cifrado e integridad
Núcleo: el APK se distribuye al dispositivo y puede ser decompilado — nunca hardcodees secretos. Almacena llaves en el Android Keystore (respaldado por hardware donde esté disponible); cifra datos locales con Jetpack Security (EncryptedSharedPreferences / EncryptedFile) o SQLCipher para Room.
- Tokens: acceso de vida corta + refresh; puerta a acciones sensibles detrás de BiometricPrompt.
- Red: Network Security Config + certificate pinning (cuidado con rotación), solo HTTPS, sin texto plano.
- Play Integrity API certifica una app/dispositivo genuina; ofuscación (R8) y detección de root son defensa en profundidad, no balas de plata.
- Conoce el vocabulario OWASP MASVS / Mobile Top 10: almacenamiento inseguro, cripto débil, etc.
20 · Accesibilidad y la frontera de AI en el dispositivo
Núcleo: distribuye para TalkBack. En Compose, establece Modifier.semantics { contentDescription = ... }, marca encabezados, agrupa nodos relacionados, respeta tamaño de objetivo táctil (48dp), Dynamic Type (unidades sp), y contraste (WCAG AA).
Modifier.clickable(onClickLabel=...),stateDescription, y merge de semántica para controles compuestos.- Prueba con Accessibility Scanner y aserciones de semántica de Compose.
Frontera: AI generativa en el dispositivo vía Gemini Nano (AICore / APIs ML Kit GenAI) y la API MediaPipe LLM Inference, más TensorFlow Lite (LiteRT) clásico para visión/audio. La compensación: privacidad (los datos se mantienen locales), offline, sin costo de servidor — contra tamaño del modelo, RAM y fragmentación de dispositivos.