Diagnosticar: informes del compilador y Layout Inspector
La estabilidad deja de ser especulación en cuanto se mide. Esta lección enseña las dos herramientas complementarias que convierten el rendimiento de Compose en una disciplina empírica: los informes que emite el compilador —ficheros de texto que declaran, clase por clase, si un tipo se consideró estable, y función por función, si un composable resultó reiniciable y saltable— y el Layout Inspector de Android Studio con sus contadores de recomposición y de saltos, que muestran en vivo qué parte del árbol trabaja de más. Se explica cómo activar los informes desde Gradle, cómo leer su notación con precisión, cómo interpretar la relación entre recomposiciones y saltos, y qué método seguir para pasar de un síntoma difuso a una causa concreta.
Todo lo anterior es teoría que se puede recitar sin entenderla. Lo que separa a quien sabe de quien cree saber es la capacidad de responder, sobre un proyecto real y en cinco minutos, a tres preguntas concretas: ¿qué tipos de mi estado son inestables y por qué exactamente?, ¿qué composables han perdido la capacidad de saltarse?, y ¿cuáles de ellos están recomponiéndose de verdad cuando el usuario toca la pantalla? Existen dos herramientas que responden a esas preguntas con datos y no con intuiciones, y son complementarias porque miran el problema desde extremos opuestos: una habla del código tal como se compiló, la otra de la app tal como se ejecuta. Aprender a leerlas convierte el rendimiento de Compose en algo que se mide, no en algo sobre lo que se opina.
- Activar los informes de estabilidad del compilador de Compose desde la configuración de Gradle.
- Leer el informe de clases y saber por qué un tipo concreto quedó marcado como inestable.
- Interpretar el informe de composables y las etiquetas de reiniciable, saltable y parámetro inestable.
- Usar los contadores de recomposición y de saltos del Layout Inspector para localizar el trabajo sobrante.
Los informes del compilador
El plugin del compilador de Compose puede emitir, al compilar, un conjunto de ficheros de texto que documentan las decisiones de estabilidad que tomó. No son un perfilador ni miden nada en ejecución: son la transcripción del razonamiento del compilador, y por eso resultan tan valiosos para entender causas.
// build.gradle.kts del modulo
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_reports")
metricsDestination = layout.buildDirectory.dir("compose_metrics")
}
Tras compilar, el directorio contiene tres piezas relevantes. Un fichero de clases que enumera cada tipo visto por el compilador con su veredicto de estabilidad y el motivo. Un fichero de composables con cada función componible, sus parámetros y si resultó reiniciable y saltable. Y un fichero de métricas en formato de datos con los recuentos agregados, útil para vigilar la evolución del proyecto entre versiones.
En proyectos que aún no han migrado a la configuración del plugin autónomo, la activación se hace pasando argumentos al compilador de Kotlin, con el mismo efecto:
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
compilerOptions.freeCompilerArgs.addAll(
"-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
project.layout.buildDirectory.get().asFile.absolutePath + "/compose_reports",
)
}
Conviene generar los informes sobre una compilación de release, o al menos con las mismas opciones que la de producción, porque ciertas optimizaciones y la presencia o ausencia de módulos afectan al resultado. Y conviene guardarlos: comparar el informe de hoy con el de hace tres meses revela regresiones de estabilidad que nadie notó al introducirlas.
Leer el informe de clases
La notación es escueta y directa. Cada clase aparece precedida de su veredicto, y cada propiedad lleva marcada su contribución a la estabilidad.
unstable class BandejaState {
stable val filtro: String
unstable val mensajes: List<Mensaje>
<runtime stability> = Unstable
}
stable class Mensaje {
stable val id: Long
stable val texto: String
}
Se lee así: BandejaState es inestable, y la razón está señalada línea a línea —filtro es estable, pero mensajes no lo es por ser una List—. La estabilidad en tiempo de ejecución resume el veredicto final. Este es el momento en que la teoría de la lección anterior se vuelve accionable: no hay que adivinar qué campo rompe la estabilidad, el informe lo señala con el dedo.
Un consejo de método: no leas el informe de clases de principio a fin. En un proyecto mediano tiene miles de líneas y la mayoría corresponde a tipos que jamás cruzan la frontera de un parámetro componible. Búscalo dirigido, filtrando por los nombres de las clases que forman el estado de las pantallas que te preocupan. El informe es un diccionario que se consulta, no un libro que se lee.
Hay un tercer veredicto que confunde al principio: un tipo puede aparecer como estabilidad parametrizada, lo que significa que su estabilidad depende de la de sus argumentos genéricos. Un contenedor genérico bien escrito será estable cuando se instancie con tipos estables e inestable cuando no, y el informe lo refleja con esa dependencia en lugar de con un veredicto fijo.
Leer el informe de composables
El segundo fichero traduce esos veredictos a consecuencias sobre las funciones.
restartable skippable fun ListaMensajes(
stable mensajes: ImmutableList<Mensaje>
stable onAbrir: Function1<Long, Unit>
)
restartable fun Bandeja(
unstable state: BandejaState
)
Nótese que el segundo parámetro de la primera función es una lambda que el informe muestra con su nombre interno de tipo función y marca como estable: las lambdas son estables cuando lo son sus capturas, y verlas marcadas como inestables es una pista valiosa de que están capturando algo que no debían.
La primera función es reiniciable y saltable: tiene scope propio y el compilador pudo generar la comparación, así que si los argumentos no cambian no se ejecuta. La segunda es reiniciable pero no saltable, y la ausencia de esa palabra es la señal que estás buscando: cada vez que su padre se recomponga, su cuerpo entero se ejecutará, aunque state sea idéntico. El parámetro culpable está marcado como inestable justo debajo.
Informe de clases
Dice por qué un tipo es inestable, campo a campo. Es la herramienta de causa: responde a la pregunta de qué hay que arreglar.
Informe de composables
Dice qué funciones perdieron la capacidad de saltar. Es la herramienta de impacto: responde a la pregunta de a quién afecta.
Layout Inspector
Dice qué recompone de verdad al usar la app. Es la herramienta de evidencia: responde a la pregunta de si importa en la práctica.
Los dos informes se complementan en una dirección concreta que conviene fijar: el de clases responde a la pregunta de qué arreglar, el de composables a la de si arreglarlo merece la pena. Un tipo inestable que solo aparece como parámetro de una función llamada una vez al abrir una pantalla no vale el esfuerzo; el mismo tipo dentro de un elemento de lista que se pinta cuarenta veces por pantallazo sí lo vale. Sin el segundo informe, el primero es una lista de deberes sin prioridad.
El Layout Inspector y los contadores
Los informes describen posibilidades; el Layout Inspector describe hechos. Con la app conectada y la inspección activa, el árbol de composición muestra para cada nodo dos números: cuántas veces se ha recompuesto y cuántas veces se ha saltado. Esa pareja es el instrumento de medida más directo que existe en Compose.
Hay una tercera columna que a menudo se ignora y que aporta información distinta: la de los cambios de argumento, que indica qué parámetro concreto se consideró diferente en la última recomposición. Cuando un nodo se recompone y no entiendes por qué, esa columna suele nombrar al culpable directamente, y muy a menudo el culpable es una lambda recreada en cada pasada o un objeto construido en el sitio de la llamada en lugar de recordado.
La lectura es interpretativa, no mecánica. Un recuento alto de recomposiciones no es malo por sí mismo si el dato realmente cambia a esa velocidad —un contador de milisegundos debe recomponerse mucho—. Lo alarmante es el patrón en el que un nodo cuyo contenido visible no ha cambiado acumula recomposiciones y cero saltos: eso es trabajo puro sin resultado. El caso opuesto, muchos saltos y pocas recomposiciones, es exactamente lo que quieres ver en un subárbol grande y estático.
flowchart TD SINT[Sintoma: la lista va a tirones] --> INSP[Layout Inspector: que nodo acumula recomposiciones sin saltos] INSP --> COMP[Informe de composables: esa funcion es saltable] COMP -->|No lo es| CLAS[Informe de clases: que parametro es inestable] CLAS --> FIX[Corregir el tipo en el estado] FIX --> MED[Volver a medir en el inspector] COMP -->|Si lo es| OTRO[El coste esta en otra parte: buscar trabajo pesado en el cuerpo] style FIX fill:#a6e3a1,color:#11111b
El método que ordena todo esto es el del diagrama y merece enunciarse explícitamente, porque invertirlo es el error habitual: empieza por la evidencia, sigue por el impacto, termina en la causa, y vuelve a medir. Quien empieza por el informe de clases acaba corrigiendo veinte tipos inestables que nunca estuvieron en un camino caliente; quien empieza por el inspector corrige dos y nota la diferencia.
Hay una cuarta herramienta que completa el cuadro cuando el problema no es de estabilidad: las pruebas de rendimiento con macrobenchmark, que miden fotogramas descartados y tiempo de arranque sobre una compilación real. Sirven para responder a la pregunta que ni los informes ni el inspector responden —si el usuario lo nota— y para convertir una mejora en un número que se puede vigilar en integración continua. La secuencia completa de un profesional es: benchmark para saber si hay problema, inspector para saber dónde, informes para saber por qué.
Añadir los informes generados a un directorio versionado o a los artefactos de la integración continua convierte una foto en una serie temporal. Con eso, un cambio que introduce un tipo inestable en el estado de una pantalla crítica deja de ser invisible y se detecta en la revisión de código, que es infinitamente más barato que descubrirlo tres versiones después mediante quejas de usuarios.
Una compilación de depuración desactiva optimizaciones, añade instrumentación y ejecuta código sin compilar de antemano, de modo que los tiempos son irreales y a menudo peores por motivos que desaparecen en producción. Los contadores de recomposición sí son válidos en depuración porque cuentan eventos lógicos, no tiempo; pero cualquier conclusión sobre fluidez debe tomarse sobre una compilación de release.
Hay un patrón de conducta que se repite en todos los equipos que sufren rendimiento y que conviene nombrar para no caer en él. Alguien nota que la app va a tirones, recuerda haber leído sobre estabilidad, y se lanza a marcar tipos con anotaciones, envolver todo en remember y sustituir listas por colecciones inmutables en cincuenta ficheros. Al terminar, la app va igual de mal, pero ahora nadie sabe por qué, porque se han hecho cincuenta cambios simultáneos sin una sola medición entre ellos. Eso no es ingeniería: es ritual. La diferencia entre ambas cosas no está en la sofisticación de las técnicas —los ritos también son sofisticados— sino en la existencia de un lazo cerrado entre la acción y la evidencia. Y lo notable de Compose es que ese lazo está insólitamente bien equipado: tienes un informe que te dice qué creyó el compilador y por qué, otro que te dice qué consecuencias tuvo esa creencia sobre cada función, y un inspector que te dice qué está pasando de verdad mientras el usuario usa la app. Con tres fuentes así, la pregunta “¿por qué va lenta esta pantalla?” deja de ser una conversación de opiniones y se convierte en una investigación con pruebas. La conclusión operativa es dura pero simple: si no puedes señalar el número que empeoraba antes de tu cambio y el número que mejora después, no has optimizado nada, has editado código y has tenido esperanza. Y la esperanza no aparece en ningún informe.
- Activa los informes en un módulo tuyo, compila y localiza los tres tipos más inestables de tu estado con el motivo exacto de cada uno.
- Busca en el informe de composables qué funciones perdieron la etiqueta de saltable por culpa de esos tipos.
- Conecta el Layout Inspector, usa la pantalla treinta segundos y anota qué nodo acumula recomposiciones con cero saltos.
- Corrige un único tipo, vuelve a compilar y a medir, y documenta el cambio en los dos números antes y después.
- Explica por qué el orden evidencia, impacto, causa produce mejores resultados que recorrer el informe de clases de arriba abajo.