Qué es Android realmente
Android no es un sistema operativo sino una pila de contratos apilados: un kernel Linux endurecido abajo, una capa de abstraccion de hardware que aisla al silicio, el runtime ART que ejecuta tu bytecode, un framework de servicios del sistema y, encima de todo, la capa que cada fabricante superpone. Esta leccion desmonta esa pila desde AOSP hacia arriba, separa lo que es codigo abierto de lo que son los servicios propietarios de Google, explica como ART compila tu app antes y durante la ejecucion con perfiles, y por que Project Treble, el Generic Kernel Image y los modulos de Mainline cambiaron para siempre quien puede actualizar que parte del sistema. Al terminar entenderas por que el dispositivo que tienes en la mano ejecuta un Android que nadie en Google compilo entero.
Preguntar qué es Android es como preguntar qué es un país: depende de si hablas del territorio, de las leyes o de la gente. Android es a la vez un proyecto de código abierto que cualquiera puede compilar, un conjunto de servicios propietarios que Google licencia, un documento de compatibilidad que define qué puede llamarse Android, y una pila de software donde cada capa la escribe una empresa distinta. Esa multiplicidad no es trivia: es la causa directa de casi todo lo que te va a resultar incómodo al programar aquí. El kernel que ejecuta tu app lo parcheó un fabricante de chips, la capa de sistema la retocó el fabricante del teléfono, y el runtime que interpreta tu bytecode puede haberse actualizado esta semana desde la tienda sin que el sistema operativo cambiase de versión. Entender esa arquitectura de responsabilidades repartidas es el primer paso para dejar de sorprenderte.
- Distinguir AOSP, los servicios de Google y las capas de los fabricantes.
- Situar el kernel Linux y por qué Android no es un
GNU/Linuxconvencional. - Entender qué hace
ARTcon tu código antes y durante la ejecución. - Comprender cómo Treble, el
GKIy Mainline reparten las actualizaciones.
Una pila, no un sistema operativo
Antes de programar nada conviene saber sobre qué se está programando, y en Android esa respuesta tiene más profundidad de la que parece.
Lo que llamamos Android es una torre de capas donde cada una habla con la de abajo a través de una interfaz estable, y cada interfaz marca una frontera de responsabilidad empresarial. Abajo del todo hay un kernel Linux —una rama LTS con parches propios: el driver binder para la comunicación entre procesos, la gestión de wakelocks, el asesino por falta de memoria— sobre el que se apoya una capa de abstracción de hardware que aísla al resto del sistema del silicio concreto. Encima viven las bibliotecas nativas y el runtime, después el framework de servicios del sistema escrito en Java y Kotlin, y arriba las aplicaciones, tanto las del sistema como la tuya.
flowchart TB APP[Apps del sistema y la tuya] FW[Framework de servicios en Java y Kotlin] ART[Runtime ART y bibliotecas nativas] HAL[Capa de abstraccion de hardware] KER[Kernel Linux con parches de Android] HW[Silicio del fabricante] APP --> FW --> ART --> HAL --> KER --> HW style APP fill:#a6e3a1,color:#11111b style ART fill:#f9e2af,color:#11111b style KER fill:#89b4fa,color:#11111b
La pieza que hace que esa torre funcione como un sistema y no como capas sueltas es binder, el mecanismo de comunicación entre procesos que Android añadió al kernel. Casi todo lo que tu app hace de interesante ocurre en otro proceso: pedir una ubicación, mostrar una ventana, reproducir audio o consultar la agenda son llamadas que cruzan una frontera de proceso y acaban en un servicio del sistema. El framework te presenta esas llamadas como métodos normales de objetos normales, pero debajo hay una transacción con un límite de tamaño, una cola de hilos y una posibilidad real de fallo. Muchas de las rarezas que encontrarás más adelante —por qué no puedes pasar un objeto grande en un Intent, por qué un cursor de base de datos ajena se lee por trozos— son consecuencias directas de esa frontera.
Todo eso es AOSP, el Android Open Source Project: código abierto que cualquiera puede descargar y compilar. Lo que no está ahí son los Google Mobile Services —Play Store, Play Services, Maps, el motor de notificaciones push—, que se licencian aparte y solo a fabricantes que superen la batería de pruebas de compatibilidad. De ahí la distinción práctica que separa a un teléfono europeo de un Amazon Fire o de un modelo doméstico chino: todos ejecutan Android en el sentido de AOSP, pero solo unos cuantos tienen el ecosistema de Google encima.
Esa separación no es un capricho comercial, tiene una función técnica clara. Al dejar fuera de AOSP los servicios de Google, esas piezas pueden actualizarse por la tienda en cualquier momento, con independencia del sistema y del fabricante. Es el mismo principio que más adelante llevará a los módulos de Mainline, y es la razón de que APIs muy usadas —ubicación fusionada, notificaciones push, autenticación— hayan podido evolucionar durante años en dispositivos que el sistema operativo dejó de actualizar hace tiempo.
Comparten el kernel y poco más. Android usa bionic como biblioteca C en lugar de glibc, tiene su propio init en vez de systemd, no monta un servidor gráfico X11 ni Wayland sino SurfaceFlinger, y su intérprete de comandos y utilidades son un toybox mínimo, no el userland de GNU. Tampoco hereda la filosofía de que todo es un fichero manipulable desde una consola: no hay un usuario con acceso total disponible en un dispositivo de fábrica, ni se espera que nadie edite ficheros de configuración a mano. Lo más importante para ti: cada app instalada recibe su propio identificador de usuario de Linux, así que el aislamiento entre aplicaciones no es una invención del framework sino permisos de fichero de toda la vida, reforzados por SELinux en modo obligatorio desde hace más de una década. Cuando el sistema te niega el acceso a un directorio, no es una API caprichosa: es el kernel diciendo que no.
ART y el viaje que hace tu código
Con la pila situada, toca seguir el camino que hace tu propio código desde que lo escribes hasta que se ejecuta, porque casi todas las decisiones de rendimiento que tomarás más adelante se apoyan en ese recorrido.
Tu Kotlin no llega al dispositivo como Kotlin. El compilador produce bytecode de la JVM, R8 lo reduce, lo ofusca y lo traduce al formato DEX, y ese DEX viaja dentro del paquete. Al otro lado espera ART, el Android Runtime, que no elige entre interpretar y compilar sino que hace las dos cosas según lo que observe. Arranca interpretando, un compilador JIT traduce a código nativo lo que se ejecuta mucho, y el runtime va anotando en un perfil qué métodos resultaron calientes. En momentos de reposo, con el dispositivo cargando, dex2oat compila ese perfil de forma anticipada para que la próxima ejecución arranque ya en código nativo.
# Estado de compilacion de una app instalada
adb shell dumpsys package dexopt | grep -A 3 com.ejemplo.app
# Forzar la compilacion guiada por perfil, como haria el sistema en reposo
adb shell cmd package compile -m speed-profile -f com.ejemplo.app
# Volver al estado de recien instalada, util para medir el arranque en frio
adb shell cmd package compile --reset com.ejemplo.app
Conviene detenerse en el formato intermedio, porque explica decisiones que verás en el sistema de compilación. DEX no es bytecode de la JVM renombrado: usa registros en lugar de una pila, agrupa todas las clases en un mismo fichero con tablas compartidas de cadenas y de tipos, y está diseñado para ocupar poco y cargarse mediante proyección en memoria. De ahí sale el famoso límite histórico de sesenta y cinco mil métodos por fichero, que hoy se resuelve partiendo en varios DEX, y de ahí sale también que reducir el número de métodos que arrastras no sea una manía de purista sino algo que se nota en el tamaño y en el arranque.
Ese bucle explica dos cosas que verás más adelante. La primera, los Baseline Profiles: un perfil que tú generas en desarrollo y empaquetas con la app, para que el primer arranque en el dispositivo del usuario no empiece desde cero. La segunda, los perfiles agregados en la nube, que Play construye con los datos de tus propios usuarios y distribuye al resto. La consecuencia práctica es que medir el arranque de tu app recién instalada en tu móvil de desarrollo no mide lo que vive un usuario real: mide el peor caso.
Falta una pieza para cerrar el recorrido, y es de dónde sale el proceso que ejecuta todo eso. Tu app no arranca una máquina virtual desde cero: el sistema mantiene vivo un proceso plantilla, encendido durante el arranque del dispositivo, que ya tiene cargadas y compartidas las clases del framework y los recursos comunes. Cuando el usuario toca tu icono, el sistema bifurca ese proceso y le da la identidad de tu app. Por eso una app arranca en cientos de milisegundos y no en varios segundos, y por eso la memoria del framework se contabiliza una sola vez aunque haya veinte apps abiertas. También por eso tu proceso hereda un estado que tú no inicializaste, un detalle que reaparecerá cuando estudies el ciclo de vida.
AOSP
El sistema en código abierto: kernel, runtime, framework y apps básicas. Es lo que se compila, se audita y se bifurca. Todo lo que aprendas aquí funciona sobre AOSP.
Servicios de Google
Play Store, Play Services, notificaciones push, ubicación fusionada. Propietarios, licenciados y con su propio ciclo de actualización, independiente del sistema.
El documento de compatibilidad
El texto que define qué puede llamarse Android, verificado por las suites de pruebas automatizadas. Es lo que impide que un fabricante rompa la API que tú usas.
Queda un matiz que cierra el cuadro y que separa a ART de una máquina virtual de servidor: aquí la compilación no persigue el máximo rendimiento en régimen permanente, sino un equilibrio entre arranque, memoria, tamaño en disco y batería. Compilar todo de forma anticipada haría que las apps ocuparan mucho más y que la instalación tardara una eternidad; no compilar nada dejaría el arranque en manos del intérprete. El sistema de perfiles es la respuesta a ese compromiso: compilar solo lo que se demuestra caliente, y hacerlo cuando el dispositivo está enchufado y ocioso.
Treble, GKI y Mainline: el sistema que se actualiza a trozos
Este es el apartado que explica por qué durante años Android tuvo fama de no actualizarse, y por qué esa fama está quedando desfasada. Durante años, actualizar Android era imposible porque todo estaba entrelazado: cambiar la versión del framework obligaba a recompilar el código del fabricante del chip. Project Treble partió esa unión con una interfaz de proveedor estable, de modo que el framework puede subir de versión sin tocar la implementación de hardware. El Generic Kernel Image dio el siguiente paso: un kernel común mantenido por Google, con lo específico de cada fabricante empujado a módulos cargables.
El orden histórico de esos tres movimientos no es casual y dibuja una estrategia clara. Treble separó al framework del proveedor de hardware, que era la unión más rígida. El kernel genérico separó el núcleo común de los añadidos de cada fabricante de chips, que era la siguiente. Y Mainline fue directamente a por el resto, sacando componentes concretos de la actualización del sistema para meterlos en la tienda. Cada paso mueve una pieza más desde el lado que actualiza despacio hacia el lado que actualiza rápido.
La comparación con el mundo del que vienes ayuda a medir el logro: un ecosistema con un único fabricante puede actualizar todos sus dispositivos a la vez porque controla la pila entera. Android tuvo que inventar fronteras estables donde otros tienen integración vertical, y estos tres proyectos son precisamente esas fronteras.
Mainline completó la jugada convirtiendo docenas de componentes del sistema —el propio ART, el códec de medios, la resolución de DNS, el stack de conectividad— en módulos actualizables que llegan por la tienda, no por una actualización del sistema. Ese reparto también reordena la responsabilidad de la seguridad. Un fallo en el descodificador de vídeo o en la resolución de nombres ya no exige que el fabricante prepare, valide y distribuya una actualización completa del sistema, proceso que históricamente tardaba meses o no llegaba nunca a los dispositivos de gama media. Hoy llega como una actualización de módulo, por el mismo canal que una app, y alcanza a dispositivos cuyo fabricante hace años que dejó de mantenerlos.
La consecuencia es contraintuitiva y conviene interiorizarla: el nivel de API que declara un dispositivo ya no describe con exactitud el código que ejecuta. Un teléfono anclado en una versión antigua puede llevar un runtime y unos códecs de hace un mes.
Porque desplaza el eje de la compatibilidad. Antes, todo dependía de la versión del sistema; hoy, una parte creciente depende de la versión del módulo, que evoluciona por su cuenta. Por eso existen las extensiones del SDK, que verás en la lección siguiente: una segunda numeración que permite preguntar si una API concreta está disponible aunque la versión del sistema diga que no debería estarlo.
Las capas de los fabricantes y quién define Android
Falta la capa de arriba del todo, la que el usuario ve y la que más te va a sorprender cuando llegue un informe de fallo.
Sobre AOSP, cada fabricante superpone su propia capa: no es un tema, es un fork parcial del framework con servicios añadidos, políticas de energía propias y a veces comportamientos que se apartan del estándar. Samsung, Xiaomi, la familia Oppo y OnePlus, Honor o el grupo Transsion —dominante en África y en el sur de Asia— mantienen cada uno la suya. Ninguna de esas capas está documentada para ti: sus diferencias no aparecen en la referencia oficial porque, formalmente, no existen.
| Aspecto | Lo que define AOSP | Lo que suele cambiar el fabricante |
|---|---|---|
| Gestión de energía | Doze y App Standby estándar | Gestores agresivos que matan procesos en segundo plano |
| Notificaciones | Canales e importancia | Agrupación, prioridad efectiva y permisos añadidos |
| Apariencia | Material 3 y color dinámico | Tipografías, formas e iconos propios |
| Ventanas | Multiventana y redimensionado | Modos de escritorio y de pantalla dividida propios |
La razón de que puedan hacerlo sin romperte es que el contrato define comportamientos observables, no implementaciones. Un fabricante puede reescribir por completo cómo decide qué proceso mata mientras siga respetando los ciclos de vida documentados; puede rehacer la persiana de notificaciones mientras los canales y sus importancias sigan funcionando. La libertad está donde no hay especificación, y ese espacio es exactamente el mapa de tus futuros informes de fallos irreproducibles.
Averiguar sobre qué capa estás corriendo es lo primero que harás cuando recibas un informe de fallo que no reproduces, y se hace con dos órdenes.
# Quien fabrico el dispositivo y que compilacion exacta lleva
adb shell getprop ro.product.manufacturer
adb shell getprop ro.build.fingerprint
# Politicas de energia en vigor ahora mismo, la fuente de la mitad de los misterios
adb shell dumpsys deviceidle | head -30
Lo único que impide que esa diversidad haga inviable programar es el contrato: el documento de definición de compatibilidad, verificado por suites de pruebas automatizadas que un fabricante debe superar para licenciar los servicios de Google. Ese contrato es la razón de que tu app funcione en un teléfono que nunca has visto.
Y también marca el límite de lo que puedes esperar. El contrato garantiza que las APIs públicas existan y se comporten como está documentado, pero no dice nada sobre lo que no está especificado: cuánto vive tu proceso cuando el usuario cambia de app, en qué orden se entregan dos notificaciones simultáneas o si una alarma llega con diez segundos de retraso. Ahí cada fabricante decide, y ahí es donde aparecen los informes de fallos imposibles de reproducir. La conclusión práctica para lo que viene después del nivel es doble: apóyate solo en lo documentado, y trata cualquier comportamiento observado en un dispositivo concreto como una hipótesis, no como una regla.
Amazon con su Fire OS, buena parte del mercado doméstico chino, los proyectos comunitarios de sistema y el creciente ecosistema centrado en privacidad ejecutan AOSP sin servicios de Google. Tu app no funcionará ahí tal cual si depende de las notificaciones push de Google, de la ubicación fusionada o del inicio de sesión con cuenta, porque todo eso vive fuera de AOSP. No es un caso raro que puedas ignorar por definición: es una decisión de producto que conviene tomar a conciencia y no por descuido.
Conviene dejar de pensar en Android como en un programa que Google escribe y empieza a pensarlo como en un estándar que Google redacta, implementa en su versión de referencia y hace cumplir mediante pruebas. Esa diferencia lo explica casi todo. Explica por qué el sistema tiene capas con interfaces tan formales entre ellas: cada frontera no es una decisión de ingeniería sino una línea de responsabilidad entre compañías que no comparten código ni calendario. Explica por qué Treble, el kernel genérico y los módulos de Mainline aparecieron uno tras otro: cada uno mueve un trozo más del sistema desde el lado del fabricante al lado de quien puede actualizarlo rápido, y cada uno recorta el margen para que un teléfono se quede congelado. Y explica por qué el aislamiento entre apps es tan sólido pese a todo el desorden de arriba: descansa en el kernel, en identificadores de usuario y en políticas obligatorias, no en la buena voluntad del framework. Cuando programas para Android no programas contra una máquina, programas contra la intersección de lo que todos esos actores acordaron respetar. Todo lo que caiga dentro de esa intersección —las APIs públicas, los comportamientos documentados, las garantías del documento de compatibilidad— funcionará en dos mil millones de dispositivos que jamás tocarás. Todo lo que caiga fuera —una API interna, una suposición sobre el fabricante, un comportamiento observado en un solo modelo— funcionará hasta que deje de hacerlo, y no sabrás por qué. Aprender Android es, en buena medida, aprender a distinguir dónde está esa frontera, porque el resto del oficio consiste en quedarse dentro y en construir con lo que el contrato garantiza.
- Con un dispositivo conectado, ejecuta
adb shell getprop ro.build.version.release,ro.build.version.sdkyro.product.manufacturer, y anota las tres respuestas. - Ejecuta
adb shell uname -ay compara la versión del kernel con la del sistema: verás dos calendarios distintos conviviendo en la misma máquina. - Lista los módulos actualizables con
adb shell pm list packages --apex-onlyy busca alguno cuyo nombre mencione el runtime o los medios: son piezas del sistema que la tienda puede actualizar. - Instala una app tuya, mide su arranque en frío, ejecuta después la compilación guiada por perfil que aparece arriba y vuelve a medir. Explica la diferencia con lo que sabes de
ART. - Argumenta en tres líneas por qué el mismo
APKpuede comportarse distinto en dos teléfonos que declaran el mismo nivel de API.