Domare la ricomposizione
Parametri stabili e meno ricomposizioni
Domare la ricomposizione è una lezione Android Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Android Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Android Academy include 4 lezioni in totale.
Ricomposizione: il comando delle prestazioni di Compose
In Jetpack Compose, l'interfaccia utente è descritta da funzioni. Quando cambia lo stato, Compose esegue la ricomposizione: riesegue i composable che leggono quello stato per aggiornare lo schermo.
La ricomposizione è normale ed economica quando è circoscritta correttamente. Tuttavia, quando avviene troppo spesso o interessa una parte troppo ampia dell'albero, diventa la causa numero uno del jank in Compose. In questa lezione imparerà a mantenere la ricomposizione limitata e poco frequente.
Visualizzare il conteggio delle ricomposizioni
Prima di correggere un problema, lo si misura. Il Layout Inspector di Android Studio mostra in tempo reale il numero di ricomposizioni per ogni composable. È inoltre possibile utilizzare le metriche del compilatore Compose o un semplice contatore di debug.
Un trucco semplice: un SideEffect incrementa un riferimento ogni volta che un composable viene ricomposto, così è possibile registrare comportamenti inattesi durante lo sviluppo.
@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.Leggere lo stato il più tardi possibile
Compose ricompone solo i composable che leggono un valore di stato. Se lo stato viene letto da un genitore, viene ricomposto l'intero genitore; se lo legge solo un figlio piccolo, viene ricomposto solo quel figlio.
Perciò sposti verso il basso dell'albero le letture dello stato. Nella versione errata, a ogni aggiornamento viene ricomposta l'intera Column; in quella corretta, la ricomposizione è isolata nell'etichetta.
// 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()}")
}
}Rimandare le letture con le lambda
Un modello molto efficace consiste nel passare, invece di un valore che cambia spesso, una lambda che lo restituisce. Il composable che alla fine invoca la lambda è l'unico a essere ricomposto.
È per questo che Modifier.offset { ... } e graphicsLayer { ... } accettano lambda: i valori di scorrimento e animazione cambiano a ogni frame, mentre la lambda mantiene la ricomposizione completamente fuori dalla fase di layout.
// 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() }
)Stabilità: perché Compose salta la ricomposizione
Compose può saltare la ricomposizione di un composable se tutti i suoi parametri sono stabili e non sono cambiati. Un tipo è stabile quando Compose può fidarsi del fatto che equals rifletta i cambiamenti effettivi e che i suoi campi pubblici non cambino senza essere rilevati.
- Stabili: primitivi,
String, data class immutabili,State. - Instabili: interfacce
List/Map, classi con campivar, tipi provenienti da moduli privi del compilatore.
Un parametro instabile forza la ricomposizione anche quando non è cambiato nulla.
// 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 valsRendere stabili i parametri
Due soluzioni comuni per i parametri instabili:
- Utilizzi le collezioni immutabili di
kotlinx.collections.immutable(ad esempioImmutableList), che Compose considera stabili. - Annoti una classe sotto il proprio controllo con
@Immutableo@Stableper garantire a Compose che non cambierà.
Compose può così saltare in sicurezza la ricomposizione quando viene passato nuovamente lo stesso oggetto.
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: non ricalcolare a ogni frame
I composable possono essere eseguiti molte volte. Qualsiasi calcolo non banale eseguito direttamente nel corpo viene effettuato a ogni ricomposizione. Lo racchiuda in remember, così verrà ricalcolato solo quando cambiano le relative chiavi.
Per i valori derivati da altro stato, preferisca derivedStateOf, che emette nuovamente il valore solo quando cambia effettivamente il risultato calcolato.
// 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()Chiavi stabili nelle liste lazy
In LazyColumn/LazyRow, assegni a ogni elemento una key stabile. Senza chiavi, l'inserimento o il riordinamento degli elementi costringe Compose a ricomporre e rimisurare elementi che in realtà non sono cambiati, perché li identifica in base alla posizione.
Con una key stabile, Compose può associare gli elementi tra un aggiornamento e l'altro e saltare quelli invariati.
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.Sollevare lo stato, propagare gli eventi verso l'alto
Anche i parametri lambda instabili possono impedire il salto della ricomposizione se a ogni ricomposizione viene creata una nuova istanza della lambda. Renda stabili i callback memorizzandoli o facendo riferimento a funzioni stabili.
In combinazione con il sollevamento dello stato (lo stato risiede nel chiamante, gli eventi risalgono), questo mantiene economici e ignorabili i composable foglia.
@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)Evitare di leggere lo stato di scorrimento/animazione troppo in alto
Un errore classico consiste nel leggere uno stato che cambia rapidamente (offset di scorrimento, avanzamento dell'animazione) in un composable di livello elevato. Questo forza la ricomposizione dell'intero sottoalbero a ogni frame.
Mantenga queste letture all'interno delle lambda di Modifier (offset {}, graphicsLayer {}, drawBehind {}), così il lavoro viene eseguito nella fase di layout o di disegno, non durante la ricomposizione. È questa la differenza tra uno scorrimento fluido a 60 fps e uno scorrimento a scatti.
// Animated color used only for drawing -> stay in the draw phase
Box(
Modifier.drawBehind {
drawRect(color = animatedColor()) // lambda read, no recomposition
}
)Checklist della ricomposizione
Quando una schermata presenta jank durante l'interazione, verifichi i seguenti punti:
- I parametri sono stabili (dati immutabili,
ImmutableList)? - Legge lo stato che cambia rapidamente il più in basso possibile, idealmente nelle lambda dei modifier?
- Le liste lazy hanno key stabili?
- I calcoli costosi sono protetti da
remember/derivedStateOf? - Il Layout Inspector ha confermato che il conteggio è effettivamente diminuito?
Ogni casella selezionata elimina lavoro inutile da ogni frame.
Verifica rapida
Si passa una List<User> a un composable e si nota che viene ricomposto anche quando i dati non sono cambiati. Qual è la soluzione più diretta?
Riepilogo: ricomposizione più limitata e meno frequente
Ha imparato a tenere sotto controllo la ricomposizione, il principale problema di prestazioni in Compose:
- Misuri il conteggio con Layout Inspector o con un contatore di debug.
- Legga lo stato il più in basso possibile; rimandi le letture con le lambda.
- Renda i parametri stabili con dati immutabili e
@Immutable/ImmutableList. - Utilizzi
rememberederivedStateOfper evitare di ricalcolare a ogni frame. - Assegni key stabili agli elementi lazy; mantenga le letture dello stato rapido nelle lambda dei modifier.
Ora passiamo dalla CPU e dalla ricomposizione alla memoria: individuare e correggere le perdite.
Domande Frequenti
La lezione «Domare la ricomposizione» è gratuita?
Sì — il testo completo di «Domare la ricomposizione» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Android Academy, passa a CoddyKit PRO. Il corso Android Academy include 4 lezioni in totale.
Cosa imparerò in «Domare la ricomposizione»?
Parametri stabili e meno ricomposizioni Eserciti Android Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Android Academy?
Non è richiesta alcuna esperienza precedente. Android Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.
Quanto tempo richiede la lezione «Domare la ricomposizione»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Android Academy?
Sì. Ogni lezione Android Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Misurare le prestazioni
- Domare la ricomposizione
- Memory leak e soluzioni
- Avvio e profili baseline