Compose Performance Profiling Guide - Layout Inspector and CPU Profiler
Compose performance optimization starts with measurement. Learn how to profile recompositions, identify bottlenecks, and optimize your UI rendering performance.
Layout Inspector - Recomposition Analysis
Android Studio's Layout Inspector shows recomposition counts and helps identify unnecessary recomposes:
// BAD: Causes excessive recomposition
@Composable
fun BadList(items: List<Item>) {
LazyColumn {
items(items) { item ->
// BAD: Entire lambda captures parent state
Text("Item: ${item.name}")
Text("Count: ${calculateCount(item)}") // Recalculates every time
}
}
}
// GOOD: Optimize with keys and stable parameters
@Composable
fun GoodList(items: List<Item>) {
LazyColumn {
items(items, key = { it.id }) { item ->
// GOOD: Stable parameters reduce recomposition
StableItemRow(item)
}
}
}
@Composable
fun StableItemRow(item: Item) {
Text("Item: ${item.name}")
Text("Count: ${rememberUpdatedState(calculateCount(item)).value}")
}
Steps to use Layout Inspector:
- Run app in Android Studio with debuggable build
- Tools → Layout Inspector
- Select your app process
- Inspect composables and check recomposition counts
@Immutable and @stable Annotations
Mark data classes and state holders to help the Compose compiler optimize:
// Mark immutable data classes
@Immutable
data class User(
val id: Int,
val name: String,
val email: String
)
// Mark stable custom objects
@Stable
class UserState(
private val _user: MutableState<User> = mutableStateOf(User(0, "", ""))
) {
val user: User
get() = _user.value
fun updateUser(user: User) {
_user.value = user
}
}
@Composable
fun UserCard(user: User) { // User is immutable, compiler can optimize
Text(user.name)
Text(user.email)
}
Compose Compiler Metrics
Enable compiler metrics to see what Compose is doing:
// build.gradle.kts
android {
composeOptions {
kotlinCompilerExtensionVersion = "1.5.x"
}
}
// Run with metrics
// ./gradlew assembleDebug -Pandroid.enableComposeCompilerMetrics=true
// Analyze output in build/compose_metrics/
Metrics files show:
-
composables_metrics.txt: Recomposition groups and stability info -
classes_metrics.txt: Compiled composable classes -
module-classes_metrics.txt: Module-level analysis
CPU Profiler with Trace API
Use Trace API for granular performance analysis:
import androidx.tracing.trace
@Composable
fun PerformanceCriticalScreen() {
trace("ScreenComposition") {
val data = rememberCoroutineScope().launch {
trace("DataLoading") {
loadData()
}
}
LazyColumn {
items(data) { item ->
trace("ItemRow-${item.id}") {
ItemRow(item)
}
}
}
}
}
Key Performance Optimization Patterns
| Optimization | Technique | Impact |
|---|---|---|
| Reduce recompose |
remember, key {}
|
High |
| Stable parameters |
@Immutable, @Stable
|
High |
| Lazy layouts |
LazyColumn, LazyRow
|
High |
| State holders | rememberXxxState() |
Medium |
| Compiler hints | Type annotations | Medium |
| Memoization | derivedStateOf {} |
Medium |
remember vs derivedStateOf
@Composable
fun OptimizedScreen(input: String) {
// Bad: Recalculates on every recomposition
val result = expensiveCalculation(input)
// Good: Only recalculates if input changes
val result = remember(input) {
expensiveCalculation(input)
}
// Best: For derived state, updates only when result changes
val memoizedResult = remember(input) {
expensiveCalculation(input)
}
val derived = derivedStateOf { memoizedResult.uppercase() }
}
8 Android app templates available on Gumroad
Top comments (0)