Cómo controlar la recomposición
Parámetros estables y menos recomposiciones
Cómo controlar la recomposición es una lección gratuita de Android Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Android Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Android Academy incluye 4 lecciones en total.
Recomposición: el control de rendimiento de Compose
En Jetpack Compose, la interfaz de usuario se describe mediante funciones. Cuando cambia el estado, Compose hace una recomposición: vuelve a ejecutar los composables que leen ese estado para actualizar la pantalla.
La recomposición es normal y barata cuando está bien delimitada. Sin embargo, cuando ocurre con demasiada frecuencia o afecta a una parte demasiado grande del árbol, se convierte en la causa principal del jank de Compose. En esta lección aprenderá a mantener la recomposición pequeña y poco frecuente.
Cómo ver los recuentos de recomposición
Antes de corregir, mida. El Layout Inspector de Android Studio muestra en tiempo real los recuentos de recomposición de cada composable. También puede usar las métricas del compilador de Compose o un contador de depuración sencillo.
Un truco simple: un SideEffect incrementa una referencia cada vez que un composable se recompone, de modo que puede registrar sorpresas durante el desarrollo.
@Composable
fun RecompositionCounter(tag: String) {
val count = remember { mutableStateOf(0) }
SideEffect { count.value++ }
Log.d("Recompose", "$tag recomposed ${count.value} times")
}
// Drop RecompositionCounter("PriceLabel") inside a composable
// to watch how often it re-runs while you interact.Lea el estado lo más tarde posible
Compose solo recompone los composables que leen un valor de estado. Si el elemento padre lee el estado, se recompone todo el padre; si solo lo lee un elemento hijo pequeño, solo se recompone ese hijo.
Por eso, desplace las lecturas de estado hacia abajo en el árbol. En este caso, la versión incorrecta recompone todo el Column en cada tic; la versión correcta lo limita a la etiqueta.
// BAD: Column reads `seconds`, so everything recomposes each second
@Composable
fun TimerBad(seconds: Int) {
Column {
ExpensiveHeader()
Text("Elapsed: $seconds")
}
}
// GOOD: only the Text reads the value via a lambda
@Composable
fun TimerGood(seconds: () -> Int) {
Column {
ExpensiveHeader()
Text("Elapsed: ${seconds()}")
}
}Aplace las lecturas con lambdas
Un patrón muy eficaz consiste en pasar, en lugar de un valor que cambia con frecuencia, una lambda que lo devuelva. El composable que finalmente invoca la lambda es el único que se recompone.
Por eso Modifier.offset { ... } y graphicsLayer { ... } reciben lambdas: los valores de desplazamiento y animación cambian en cada fotograma, y la lambda mantiene la recomposición fuera de la fase de diseño.
// Passing the value: parent recomposes every frame of scroll
Box(Modifier.offset(y = scrollOffset.dp))
// Passing a lambda: skips recomposition, updates in the layout phase
Box(Modifier.offset { IntOffset(x = 0, y = scrollOffset.roundToInt()) })
// Same idea for alpha/scale during animation:
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.graphicsLayer { alpha = animatedAlpha() }
)Estabilidad: por qué Compose omite recomposiciones
Compose puede omitir la recomposición de un composable si todos sus parámetros son estables y no han cambiado. Un tipo es estable cuando Compose puede confiar en que equals refleja los cambios reales y que sus campos públicos no cambian sin que se detecte.
- Estables: tipos primitivos,
String, clases de datos inmutables yState. - Inestables: las interfaces
List/Map, las clases con camposvary los tipos de módulos sin el compilador.
Un parámetro inestable fuerza la recomposición aunque no haya cambiado nada.
// UNSTABLE: List is an interface; Compose cannot assume immutability,
// so UserList recomposes even if the contents are identical.
@Composable
fun UserList(users: List<User>) { /* ... */ }
data class User(val id: Long, val name: String) // stable: all valsHaga estables los parámetros
Dos soluciones habituales para los parámetros inestables:
- Use colecciones inmutables de
kotlinx.collections.immutable(por ejemplo,ImmutableList), que Compose trata como estables. - Anote con
@Immutableo@Stableuna clase que controle para garantizarle a Compose que no cambiará.
Ahora Compose puede omitir de forma segura la recomposición cuando se vuelve a pasar la misma instancia.
import kotlinx.collections.immutable.ImmutableList
import androidx.compose.runtime.Immutable
@Immutable
data class UiState(
val title: String,
val users: ImmutableList<User>
)
// Stable parameter -> Compose can skip this when state is unchanged
@Composable
fun UserList(users: ImmutableList<User>) { /* ... */ }remember: no recalcule en cada fotograma
Los composables pueden ejecutarse muchas veces. Cualquier cálculo no trivial realizado directamente en el cuerpo se ejecuta en cada recomposición. Envuélvalo en remember para que solo se recalcule cuando cambien sus claves.
Para valores derivados de otro estado, prefiera derivedStateOf, que solo vuelve a emitir cuando el resultado calculado cambia realmente.
// Recomputes the sorted list only when `items` changes
val sorted = remember(items) { items.sortedBy { it.name } }
// derivedStateOf: only triggers readers when the BOOLEAN flips,
// not on every scroll pixel
val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 5 }
}
if (showButton) ScrollToTopButton()Claves estables en listas diferidas
En LazyColumn/LazyRow, proporcione una key estable para cada elemento. Sin claves, insertar o reordenar elementos obliga a Compose a recomponer y volver a medir elementos que en realidad no cambiaron, porque los identifica por su posición.
Con una clave estable, Compose puede asociar los elementos entre actualizaciones y omitir los que no cambiaron.
LazyColumn {
items(
items = users,
key = { user -> user.id } // stable identity
) { user ->
UserRow(user)
}
}
// Now adding a user at the top reuses existing rows
// instead of recomposing the whole list.Eleve el estado y envíe los eventos hacia arriba
Los parámetros lambda inestables también pueden impedir que se omitan recomposiciones si se crea una nueva instancia de lambda en cada recomposición. Estabilice las devoluciones de llamada recordándolas o haciendo referencia a funciones estables.
Combinado con la elevación del estado (el estado vive en quien realiza la llamada y los eventos fluyen hacia arriba), esto mantiene los composables hoja ligeros y aptos para omitirse.
@Composable
fun SearchBar(query: String, onQueryChange: (String) -> Unit) {
TextField(value = query, onValueChange = onQueryChange)
}
// In the caller, a remembered lambda keeps the reference stable:
val onChange = remember { { newValue: String -> viewModel.setQuery(newValue) } }
SearchBar(query = query, onQueryChange = onChange)Evite leer el estado de desplazamiento o animación demasiado arriba
Un error clásico es leer un estado que cambia rápidamente (el desplazamiento o el progreso de una animación) en un composable de nivel alto. Esto obliga a recomponer todo el subárbol en cada fotograma.
Mantenga esas lecturas dentro de lambdas de Modifier (offset {}, graphicsLayer {}, drawBehind {}) para que el trabajo se realice en la fase de diseño o dibujo, no durante la recomposición. Esta es la diferencia entre un desplazamiento fluido a 60 fps y uno entrecortado.
// Animated color used only for drawing -> stay in the draw phase
Box(
Modifier.drawBehind {
drawRect(color = animatedColor()) // lambda read, no recomposition
}
)Lista de comprobación de la recomposición
Cuando una pantalla tenga jank durante la interacción, repase esta lista:
- ¿Son estables los parámetros (datos inmutables,
ImmutableList)? - ¿Lee el estado que cambia rápidamente lo más abajo posible, idealmente en lambdas de modificadores?
- ¿Tienen las listas diferidas claves estables?
- ¿Están los cálculos costosos detrás de
remember/derivedStateOf? - ¿El Layout Inspector confirmó que el recuento realmente disminuyó?
Cada casilla que marque elimina trabajo innecesario de todos los fotogramas.
Comprobación rápida
Pasa un List<User> a un composable y observa que se recompone aunque los datos no hayan cambiado. ¿Cuál es la solución más directa?
Repaso: una recomposición más pequeña y menos frecuente
Ha aprendido a controlar el principal problema de rendimiento de Compose: la recomposición:
- Mida los recuentos con Layout Inspector o con un contador de depuración.
- Lea el estado lo más abajo posible; aplace las lecturas con lambdas.
- Haga que los parámetros sean estables mediante datos inmutables y
@Immutable/ImmutableList. - Use
rememberyderivedStateOfpara evitar recalcular en cada fotograma. - Proporcione claves estables a los elementos diferidos; mantenga las lecturas de estados rápidos en lambdas de modificadores.
A continuación pasaremos de la CPU y la recomposición a la memoria: localizar y corregir fugas.
Preguntas frecuentes
¿La lección «Cómo controlar la recomposición» es gratis?
Sí — el texto completo de «Cómo controlar la recomposición» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Android Academy, actualiza a CoddyKit PRO. El curso de Android Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Cómo controlar la recomposición»?
Parámetros estables y menos recomposiciones Practicas Android Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Android Academy?
No se requiere experiencia previa. Android Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.
¿Cuánto tiempo toma la lección «Cómo controlar la recomposición»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Android Academy?
Sí. Cada lección de Android Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Medición del rendimiento
- Cómo controlar la recomposición
- Fugas de memoria y soluciones
- Perfiles de inicio y de referencia