wandres.dev
EL ECOSISTEMA · AOSP, versiones y fragmentación

Versiones y niveles de API

Android arrastra dos numeraciones paralelas que la gente confunde a diario: la version comercial que ve el usuario y el nivel de API contra el que programas tu. Esta leccion explica por que existen las dos, como se corresponden, que significa exactamente un nivel de API como contrato de superficie, y como se consulta en tiempo de ejecucion. Ademas cubre las dos numeraciones adicionales que Android ha ido acumulando: los niveles menores de API que estrenaron el 36.1 al romper la cadencia anual, y las extensiones del SDK que permiten preguntar por una API concreta con independencia de la version del sistema. Al terminar sabras leer cualquier tabla de compatibilidad de la documentacion oficial sin dudar.

⏱ 15 min

Hay dos números que describen la misma versión de Android y casi nunca coinciden: el que el usuario ve en los ajustes de su teléfono y el que tú escribes en el fichero de compilación. Uno es marketing y el otro es un contrato. La versión comercial —Android 16, Android 17— sirve para que alguien sepa si su móvil está al día; el nivel de API —36, 37— identifica una superficie de programación concreta, congelada, contra la que puedes compilar con garantías. Esa dualidad ya sería suficiente lío, pero Android le ha añadido en los últimos años dos numeraciones más: los niveles menores, estrenados cuando la plataforma pasó a publicar dos entregas al año, y las extensiones del SDK, que existen porque hoy media plataforma se actualiza por la tienda. Aprender a leer las cuatro es lo que te permitirá entender una tabla de compatibilidad de un vistazo.

🎯 Al terminar esta lección sabrás
  • Distinguir versión comercial, nivel de API y nombre en clave interno.
  • Leer y consultar Build.VERSION.SDK_INT con criterio en tiempo de ejecución.
  • Entender los niveles menores de API y por qué apareció el 36.1.
  • Usar las extensiones del SDK para preguntar por una API concreta.

Dos numeraciones que no son la misma cosa

Empecemos por la pareja que aparece en cualquier documentación, porque de ella se derivan las otras dos.

La versión comercial cambia una vez al año y es un número redondo, pensado para aparecer en una nota de prensa. El nivel de API es un entero que se incrementa cada vez que la superficie pública de programación cambia de forma significativa, y su valor real está en que es inmutable: el nivel 34 describe para siempre el mismo conjunto de clases, métodos y comportamientos, aunque el teléfono que lo ejecuta reciba parches durante años. Por debajo circula además un nombre en clave interno —los postres siguen existiendo dentro de AOSP aunque Google dejase de anunciarlos— que verás en las constantes del propio SDK.

Versión comercial Nivel de API Nombre en clave Publicación
Android 13 33 Tiramisu 2022
Android 14 34 UpsideDownCake 2023
Android 15 35 VanillaIceCream 2024
Android 16 36 Baklava junio de 2025
Android 16 QPR2 36.1 diciembre de 2025
Android 17 37 Cinnamon Bun junio de 2026

Esa tabla esconde el cambio más relevante de la última década: el salto de Android 15 a 16 se adelantó al segundo trimestre, y desde entonces hay una entrega mayor a mitad de año y una menor a final. Volveremos sobre el calendario en la última lección del nivel; aquí importa la consecuencia numérica, que es la fila del 36.1.

📝
Lo que un nivel de API sí y no promete

Un nivel de API te promete que un conjunto de firmas existe y que unos comportamientos documentados se cumplen. No te promete que la implementación sea idéntica en todos los dispositivos, ni que las clases marcadas como internas sigan ahí, ni que un fabricante no haya alterado un comportamiento no especificado. Programar contra el nivel de API es programar contra lo documentado; todo lo demás es observación empírica que caducará.

Consultar la versión en tiempo de ejecución

El acceso canónico es Build.VERSION.SDK_INT, un entero que devuelve el nivel de API del dispositivo. Se compara siempre contra las constantes de Build.VERSION_CODES, nunca contra un número escrito a mano, porque el nombre documenta la intención y sobrevive a la revisión de código.

import android.os.Build
import androidx.annotation.RequiresApi

// Comprobacion clasica: la rama moderna y la de respaldo.
fun tieneApiModerna(): Boolean =
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
fun usarApiDeAndroid16() {
    // El compilador y lint ya saben que este cuerpo solo corre en 36 o superior.
}

fun rutaSegura() {
    if (tieneApiModerna()) usarApiDeAndroid16() else respaldo()
}

Hay dos propiedades más en el mismo sitio que casi nadie mira y que resuelven un problema real. Build.VERSION.CODENAME vale la cadena REL en cualquier versión publicada y el nombre en clave cuando el dispositivo ejecuta una preview; Build.VERSION.PREVIEW_SDK_INT vale cero en versiones finales y un número positivo en las de desarrollo. Juntas te permiten distinguir un dispositivo con la beta de la próxima versión de uno con la versión estable, algo indispensable si mandas telemetría y no quieres que un puñado de dispositivos de prueba te contamine las estadísticas de campo.

fun esVersionDePreview(): Boolean =
    Build.VERSION.PREVIEW_SDK_INT > 0 || Build.VERSION.CODENAME != "REL"

Conviene además desactivar una intuición falsa: la secuencia de niveles no es perfectamente paralela a la de versiones comerciales. Ha habido niveles asignados a variantes que nunca fueron una versión de móvil, y ha habido versiones comerciales que cubrieron dos niveles porque una revisión de mantenimiento introdujo APIs. Por eso el mapa entre ambas numeraciones se consulta en una tabla y no se calcula con una fórmula: no la hay.

La anotación RequiresApi no genera comprobación alguna en tiempo de ejecución: es información para el análisis estático, que a cambio te avisa si llamas al método desde un camino sin proteger. La pareja SDK_INT más RequiresApi es el patrón que verás en todo el código de Jetpack, y conviene adoptarlo desde el primer día en lugar de dispersar comparaciones numéricas sueltas.

🔢

SDK_INT

El nivel de API del dispositivo, como entero. La fuente de verdad para decidir en tiempo de ejecución qué rama tomar.

🧾

VERSION_CODES

Las constantes con nombre. Escribir la constante en vez del número convierte una comparación críptica en una frase legible.

🛡️

RequiresApi

Una anotación para lint y el compilador. No protege en ejecución, pero impide que la protección se te olvide.

Niveles menores: el 36.1 que rompió la tradición

Cuando la plataforma pasó a publicar una entrega menor a final de año, apareció un problema aritmético: esa entrega traía APIs nuevas pero no merecía un nivel de API entero. La solución fue estrenar la primera versión fraccionaria de la historia de Android, el 36.1, y con ella una segunda propiedad de consulta que codifica mayor y menor en un solo entero.

import android.os.Build

// SDK_INT sigue devolviendo 36 en una entrega menor de Android 16.
// SDK_INT_FULL distingue 36.0 de 36.1.
fun soportaNovedadesDeLaMenor(): Boolean =
    Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.BAKLAVA_1

La regla mental es sencilla: SDK_INT responde a qué versión mayor de Android es esto, y SDK_INT_FULL responde a qué entrega exacta. Para la inmensa mayoría del código sigue bastando la primera; la segunda solo hace falta cuando dependes de una API que llegó en una entrega menor. Ojo con un detalle práctico: la propiedad completa solo existe a partir de la propia entrega que la introdujo, así que en dispositivos anteriores hay que llegar a ella tras comprobar SDK_INT.

flowchart TD
Q[Necesito una API concreta]
Q --> A[Llego en una version mayor]
Q --> B[Llego en una entrega menor]
Q --> C[Viene de un modulo actualizable]
A --> AR[Comprobar SDK_INT]
B --> BR[Comprobar SDK_INT_FULL]
C --> CR[Comprobar la extension del SDK]
style AR fill:#a6e3a1,color:#11111b
style BR fill:#f9e2af,color:#11111b
style CR fill:#89b4fa,color:#11111b

Extensiones del SDK: la numeración que ignora la versión

Como viste en la lección anterior, buena parte del sistema viaja hoy en módulos que la tienda actualiza por su cuenta. Eso rompe la equivalencia entre versión del sistema y APIs disponibles: un dispositivo antiguo puede tener una API que su nivel de API no contempla, porque llegó dentro de un módulo. Para eso existen las extensiones del SDK, una numeración independiente que se consulta con SdkExtensions.getExtensionVersion.

import android.os.Build
import android.os.ext.SdkExtensions

fun photoPickerDisponible(): Boolean = when {
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> true
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ->
        SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= 2
    else -> false
}

Leído en voz alta: si el sistema es lo bastante moderno, la API está y punto; si no lo es pero admite extensiones, se pregunta por la versión de extensión concreta. Es la forma más precisa de expresar una dependencia y sustituye a la vieja costumbre de exigir una versión entera del sistema por una sola función.

Las extensiones se numeran por familia y esa familia se identifica con el nivel de API en el que nació el mecanismo, de ahí que en el ejemplo se pregunte por la constante de una versión antigua: no se está preguntando por esa versión del sistema, sino por el contador de extensiones asociado a ella. Es la parte que más confunde al leerlo por primera vez, y se aclara en cuanto asumes que ese argumento es un identificador de familia y no una versión.

⚠️
Preguntar de más también cuesta

El vicio contrario al de no comprobar nada es comprobar más de lo necesario. Exigir un nivel de API entero porque una sola función tuya lo pide, cuando esa función podría tener un camino alternativo o venir por una extensión, te deja fuera de millones de dispositivos por una funcionalidad secundaria. La disciplina correcta es preguntar por la capacidad concreta y al nivel más fino disponible, no por la versión del sistema como aproximación gruesa de todo.

El nivel de API es la unidad de tiempo con la que Android piensa

Hay un cambio de mentalidad que separa a quien programa Android de quien lo padece, y es dejar de ver estos números como burocracia para verlos como lo que son: el mecanismo con el que una plataforma sin control sobre su hardware consigue evolucionar sin romper a nadie. Un nivel de API es una fotografía inmutable de una superficie de programación, y su inmutabilidad es exactamente lo que permite que Google cambie el sistema por debajo durante diez años sin que tu app de 2019 deje de arrancar. Cada número congelado es una promesa retroactiva. Y las numeraciones que se han ido añadiendo encima no son un accidente ni desorden acumulado: cada una apareció cuando una pieza del sistema empezó a moverse a un ritmo distinto del resto. El nivel menor nació el día que la plataforma decidió entregar dos veces al año, porque el calendario dejó de caber en un entero. Las extensiones nacieron el día que módulos enteros del sistema empezaron a actualizarse por la tienda, porque preguntar por la versión del sistema dejó de responder a la pregunta que de verdad importaba —tengo esta API o no—. Cuando entiendes eso, dejas de memorizar tablas y empiezas a razonar: qué se mueve a qué velocidad, y por tanto qué número tengo que consultar. Esa es la diferencia entre copiar una comprobación de un foro y saber exactamente qué estás preguntando y a quién. Android no tiene una versión: tiene un conjunto de piezas con relojes distintos, y estos números son cómo se lee cada reloj.

⚔️ Lee los relojes de un dispositivo real
  1. Con un dispositivo o emulador conectado, obtén su nivel de API con adb shell getprop ro.build.version.sdk y su versión comercial con ro.build.version.release; contrasta ambos con la tabla de esta lección.
  2. Escribe una función que devuelva un texto describiendo el dispositivo usando SDK_INT y las constantes con nombre, sin ningún número literal en el código.
  3. Busca en la documentación oficial una API que hayas usado y localiza en qué nivel de API se introdujo; comprueba si además tiene una versión de extensión asociada.
  4. Implementa una comprobación en tres ramas como la del selector de fotos: versión moderna, extensión disponible y respaldo. Justifica por qué el orden de las ramas importa.
  5. Explica en tres líneas por qué exigir un nivel de API entero más alto de lo necesario, cuando bastaba con comprobar una extensión, te cuesta usuarios reales.