Baseline Profiles en Android: qué mueve realmente tu tiempo de arranque en frío en 2026
Una guía práctica de los Baseline Profiles de Android: cómo generarlos con Macrobenchmark, conectarlos en Gradle, medir la ganancia real y las otras tres cosas que mueven el arranque en frío.
El arranque en frío es la cifra de rendimiento que todo usuario siente y que casi nadie mide. Tocas el icono, hay un instante de pantalla en blanco o una pantalla de bienvenida estirada, y luego aparece la aplicación. Por debajo de 500 ms nadie lo nota. Pasado un segundo, se percibe como “lento” aunque cada pantalla posterior sea instantánea: las primeras impresiones se quedan grabadas, y el icono de la aplicación es la primera.
La mayor parte de esa cifra no tiene que ver con tu diseño ni con tu consulta a la base de datos. Es el compilador JIT haciendo trabajo frío e interpretado sobre las rutas de código más frecuentadas de tu aplicación, sin ningún perfil contra el que optimizar. Los Baseline Profiles existen para saltarse ese calentamiento, y son una de las pocas herramientas de rendimiento de Android que cuestan una tarde una vez y siguen pagando dividendos en cada instalación posterior.
Qué es en realidad un Baseline Profile
Un Baseline Profile es un archivo de texto —baseline-prof.txt— que enumera las clases y métodos que tu aplicación toca durante sus flujos más comunes: el arranque en frío, y cualquier otro recorrido que elijas perfilar. Lo envías dentro del APK/AAB, y el Android Runtime (ART) lo usa para precompilar esos métodos por adelantado en lugar de interpretarlos o compilarlos con JIT en el primer uso.
El efecto es acotado pero real: no hace tu código más rápido, elimina el impuesto de interpretación sobre el código específico que se ejecuta durante el arranque. En Play, Google reporta que esto suele recortar el tiempo de arranque en frío entre un 20-30% en aplicaciones que aún no tenían uno; tu ganancia depende por completo de cuánto de tu ruta de arranque quede realmente cubierta.
Generar uno con Macrobenchmark
El perfil tiene que salir de una ejecución real e instrumentada; no se puede escribir a mano de forma útil. La biblioteca androidx.benchmark.macro hace esto:
// build.gradle.kts (módulo baselineprofile)
plugins {
id("androidx.baselineprofile")
}
dependencies {
baselineProfile(project(":app"))
}
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generate() = rule.collect(
packageName = "com.mfk.hydrame",
includeInStartupProfile = true,
) {
pressHome()
startActivityAndWait()
// Ejecuta los flujos que merece la pena precompilar, no solo el splash.
device.findObject(By.text("Log a glass")).click()
device.waitForIdle()
}
}
Ejecuta la tarea de Gradle :app:generateBaselineProfile en un dispositivo real o un emulador razonablemente rápido (la generación del perfil necesita una compilación instrumentada casi-root, que el plugin gestiona). Registra cada clase tocada durante esa ejecución guionizada y escribe baseline-prof.txt en app/src/main/, donde el plugin de baseline profile de AGP lo recoge automáticamente para los builds de release.
La parte que la gente se salta: guioniza más allá de la pantalla de bienvenida. Si el cuello de botella real es el primer renderizado de una lista con datos de Room, o la primera llamada al provider de un widget, incluye esa interacción en el bloque collect. Un perfil que solo cubre onCreate() se pierde la mayor parte de la ganancia real.
Medir la ganancia honestamente
No confíes en un cronómetro y tu pulgar. Usa StartupTimingMetric en una prueba macrobenchmark separada, ejecútala contra un build sin el perfil y un build con él, y compara las medianas con suficientes iteraciones para que el ruido se promedie en ambas direcciones:
@Test
fun startupCompilationModes() {
benchmarkRule.measureRepeated(
packageName = "com.mfk.hydrame",
metrics = listOf(StartupTimingMetric()),
iterations = 10,
startupMode = StartupMode.COLD,
compilationMode = CompilationMode.Partial(baselineProfileMode = BaselineProfileMode.Require),
) {
pressHome()
startActivityAndWait()
}
}
Cambia compilationMode a CompilationMode.None() para la ejecución de comparación de referencia. Diez iteraciones es un mínimo razonable: el estado térmico del dispositivo y los procesos en segundo plano añaden ruido suficiente como para que tres ejecuciones te engañen en cualquier dirección.
Lo que los Baseline Profiles no arreglan
No son un sustituto de reducir de verdad el trabajo de arranque. Si tu Application.onCreate() inicializa tres SDK, abre la base de datos de forma anticipada e infla una primera pantalla pesada, un perfil hace que ese trabajo se interprete más rápido; no hace que haya menos trabajo. Las ganancias se suman con esto, no en su lugar:
- Diferir la inicialización no crítica. Todo lo que no se necesita para el primer frame —SDKs de analítica, programación de WorkManager no esencial, obtención de configuración remota— va en un dispatcher en segundo plano una vez que la UI es visible, no en
onCreate(). - R8 full mode en los builds de release. Reducir y optimizar el bytecode significa que hay menos código para que ART recorra desde el principio, con o sin perfil.
- Singletons perezosos en lugar de anticipados. Una instancia de base de datos
by lazyque se inicializa en el primer acceso a un DAO no te cuesta nada en el arranque si la primera pantalla aún no la necesita.
Descubrí esto construyendo el widget de pantalla de inicio de Hydrame: el propio arranque en frío del widget (un contexto de proceso separado, sin una Application ya caliente en la que apoyarse) era más lento que el de la aplicación, porque ninguna ruta de código del widget estaba en el perfil. Añadir un escenario específico del widget al script de Macrobenchmark, no solo la actividad principal de la aplicación, cerró la mayor parte de esa brecha.
Cuándo merece la pena la tarde
Si tu aplicación ya es rápida, un Baseline Profile es un extra agradable. Si el arranque en frío es la única métrica en amarillo en el panel de vitals de Play Console, suele ser la tarde de mayor rendimiento que puedes invertir: sin cambios de arquitectura, sin riesgo funcional, solo un archivo generado y un plugin de Gradle. Genéralo una vez por versión a partir de tus rutas críticas reales, mantén la prueba de Macrobenchmark en CI para que un rediseño no supere silenciosamente lo que cubre el perfil, y trata la cifra de arranque en frío como cualquier otra regresión: algo que vigilas, no algo que arreglas una vez y olvidas.
// Lecturas relacionadas
Más del diario
Jetpack DataStore en 2026: migrar desde SharedPreferences sin perder un solo ajuste
Una guía práctica para pasar de SharedPreferences a Jetpack DataStore en Android — las trampas asíncronas, la ruta de migración que preserva los valores existentes y cómo probarla.
Construyendo Mintly: mantener un temporizador de enfoque preciso cuando Android quiere matar el proceso
Un temporizador Pomodoro en ejecución tiene un problema de fiabilidad más difícil que un recordatorio puntual. Así es como Mintly sobrevive al modo Doze, a la muerte del proceso y al desfase con la pantalla apagada, con un servicio en primer plano y una hora de fin en tiempo real.
Escaneo de códigos de barras con ML Kit en Android en 2026: cómo Stocky añade un artículo a la despensa en menos de un segundo
Una guía práctica 2026 del escaneo de códigos de barras en el dispositivo con ML Kit y CameraX: ajuste de formatos, búsqueda de producto sin conexión, y las matemáticas del uso parcial.