Recomposition là một cơ chế của Jetpack Compose tự động xây dựng lại các phần của giao diện người dùng khi dữ liệu thay đổi, mà không cần cập nhật View thủ công. Khi một biến trạng thái mà hàm Composable phụ thuộc vào thay đổi giá trị, Compose chỉ khởi động lại hàm đó, để phần còn lại của cây UI không bị ảnh hưởng. Theo Google Android Developers, 2026, hiểu đúng về Recomposition cho phép giảm 40–60% số lần vẽ lại không cần thiết.
Điểm Chính
Recomposition là việc thực thi lại các hàm Composable đã tham gia Composition trước đó, với các giá trị tham số hoặc trạng thái mới. Mục tiêu chính của tái cấu trúc là đồng bộ hóa cây UI với dữ liệu hiện tại mà không cần xây dựng lại toàn bộ giao diện từ đầu. Không giống như Composition, chỉ xảy ra một lần, Recomposition có thể được kích hoạt hàng trăm lần trong vòng đời của một màn hình.
Recomposition hoạt động theo nguyên tắc vô hiệu hóa thông minh: Compose theo dõi các đối tượng State mà mỗi hàm Composable đọc và đánh dấu để khởi động lại chỉ những hàm có phụ thuộc thay đổi. Điều này đạt được thông qua hệ thống snapshot ghi lại tất cả các thao tác đọc State trong quá trình thực thi và Composer ánh xạ các phụ thuộc này đến các hàm cụ thể.
Điều quan trọng cần hiểu: tái cấu trúc không có nghĩa là vẽ lại màn hình ngay lập tức. Compose hoạt động trong ba giai đoạn: Composition (xây dựng mô tả UI), Layout (tính toán kích thước và vị trí), và Drawing (kết xuất trên canvas). Nếu sau khi tái cấu trúc, kích thước và vị trí của các phần tử không thay đổi, giai đoạn Layout có thể được bỏ qua. Nếu giao diện trực quan không thay đổi — Drawing được bỏ qua. Kiến trúc ba giai đoạn này đảm bảo chi phí tối thiểu cho mỗi lần cập nhật UI.
Có ba trình kích hoạt chính của tái cấu trúc. Đầu tiên là sự thay đổi của đối tượng State được đọc trong thân hàm Composable. Khi mutableStateOf hoặc derivedStateOf thay đổi giá trị của nó, tất cả các hàm đã đăng ký đọc State này trong composition trước đó được đánh dấu để khởi động lại.
Trình kích hoạt thứ hai là thay đổi tham số của hàm Composable khi được gọi từ hàm cha. Nếu hàm cha truyền một giá trị mới (ví dụ: văn bản hoặc số đã thay đổi), hàm con sẽ được khởi động lại, ngay cả khi nó không đọc State nội bộ. Compose so sánh các giá trị tham số mới và cũ qua equals, và nếu chúng bằng nhau — hàm có thể được bỏ qua.
Trình kích hoạt thứ ba là thay đổi CompositionLocal qua CompositionLocalProvider. Tất cả các hàm đọc CompositionLocal qua .current được khởi động lại khi nhà cung cấp thay đổi. Cơ chế này được MaterialTheme sử dụng: chuyển đổi chủ đề (sáng/tối) gây ra tái cấu trúc của tất cả các thành phần đọc MaterialTheme.colorScheme.
@Composable
fun RecompositionDemo() {
var counter by remember { mutableStateOf(0) }
var text by remember { mutableStateOf("Hello") }
Column {
Text("Counter: $counter") // recomposition when counter changes
Text("Message: $text") // recomposition when text changes
Button(onClick = { counter++ }) {
Text("+1")
}
Button(onClick = { text = "World" }) {
Text("Change Text")
}
}
}
Nhấp vào nút +1 thay đổi counter, gây ra tái cấu trúc chỉ của dòng Text đầu tiên và chính Column. Dòng Text thứ hai hiển thị text không khởi động lại. Sự cô lập này là kết quả của hệ thống snapshot: mỗi hàm Composable chỉ biết về các đối tượng State mà nó đã đọc.
Tối ưu hóa tái cấu trúc bắt đầu bằng việc chọn cấu trúc dữ liệu phù hợp. Sử dụng bộ sưu tập bất biến (listOf, mapOf) thay vì có thể thay đổi (mutableListOf). Compose so sánh tham số qua equals, và nếu một bộ sưu tập đã thay đổi nhưng equals trả về true — hàm sẽ không khởi động lại. Đối với bộ sưu tập có thể thay đổi, hãy sử dụng SnapshotStateList, thực hiện theo dõi thay đổi chính xác ở cấp độ phần tử.
Kỹ thuật thứ hai là trích xuất các phần ổn định của UI vào các hàm Composable riêng biệt. Nếu một phần của màn hình không phụ thuộc vào trạng thái thay đổi thường xuyên, hãy trích xuất nó thành một hàm riêng với các tham số. Khi tái cấu trúc xảy ra, hàm ổn định nhận các tham số giống nhau, Compose so sánh chúng và bỏ qua thực thi. Điều này hiệu quả hơn so với khởi động lại phần đó như một phần của hàm lớn nơi một số tham số đã thay đổi.
Kỹ thuật thứ ba là khóa trong LazyColumn. Luôn chỉ định khóa cho các mục trong LazyColumn, LazyGrid và các container lười khác. Khóa cho phép Compose xác định các phần tử khi danh sách thay đổi: thêm, xóa hoặc sắp xếp lại. Nếu không có khóa, Compose khởi động lại tất cả các mục trong danh sách khi có bất kỳ thay đổi nào, điều này trên danh sách lớn gây ra suy giảm hiệu suất đáng kể.
// Optimized structure: stable parts extracted separately
@Composable
fun OptimizedScreen(items: List<Item>) {
Column {
Header() // does not depend on items — no recomposition
Spacer(modifier = Modifier.height(8.dp))
LazyColumn {
items(items, key = { it.id }) { item ->
ItemRow(item = item) // recomposition only for changed items
}
}
}
}
@Composable
fun Header() {
Text("Item list", style = MaterialTheme.typography.headlineMedium)
}
@Composable
fun ItemRow(item: Item) {
Text(item.title)
}
Skipping là một cơ chế trong đó Compose bỏ qua thực thi của một hàm Composable nếu tất cả các tham số của nó không thay đổi. Để skipping hoạt động chính xác, các kiểu tham số phải ổn định. Trình biên dịch Kotlin đánh dấu là ổn định: các kiểu nguyên thủy (Int, Float, Boolean), String, hàm lambda và các lớp có tất cả các trường ổn định và là val.
Stability là chú thích @Stable hoặc @Immutable có thể được thêm vào các lớp dữ liệu tùy chỉnh. Nếu một lớp chứa trường có thể thay đổi (var), trình biên dịch coi nó là không ổn định và Compose sẽ không thể bỏ qua các hàm có tham số như vậy. Đối với các lớp có var, hãy sử dụng @Stable nếu bạn đảm bảo rằng thông báo thay đổi sẽ được gửi qua hệ thống snapshot.
Bạn có thể kiểm tra độ ổn định bằng cờ trình biên dịch -P "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=. /reports". Nó tạo ra một báo cáo với danh sách tất cả các hàm Composable và tham số của chúng cho biết độ ổn định. Nếu một tham số không ổn định — skipping là không thể cho hàm đó và nó sẽ khởi động lại ở mỗi lần tái cấu trúc của cha.
| Kiểu | Độ ổn định | Skipping |
|---|---|---|
| Int, Float, Boolean | Ổn định | Có |
| String | Ổn định | Có |
| Lambda | Ổn định | Có |
| data class với trường val | Ổn định | Có |
| data class với trường var | Không ổn định | Không |
| List<String> | Không ổn định | Không |
Lưu ý: List<String> được coi là không ổn định vì nó là một giao diện, không phải triển khai cụ thể. Sử dụng immutableListOf() từ thư viện Kotlin Collections Immutable hoặc bọc danh sách trong một lớp @Stable. Lambda luôn ổn định vì equals của nó chỉ so sánh tham chiếu và khi một lambda mới được tạo tại vị trí gọi, hàm cha cũng khởi động lại.
Để giám sát tái cấu trúc, Android Studio cung cấp Layout Inspector với chế độ Compose Recomposition Counts. Trong chế độ này, mỗi hàm Composable hiển thị số lần tái cấu trúc và lý do khởi động lại. Điều này cho phép bạn nhanh chóng tìm thấy các hàm tái cấu trúc quá thường xuyên và xác định nguyên nhân gốc rễ — tham số không ổn định hoặc phụ thuộc State không cần thiết.
Các công cụ bổ sung: Compose Metrics (thu thập thống kê qua kiểm tra instrumentation) và Recomposition Timer (đo thời gian thực thi của mỗi hàm). Google khuyến nghị bật các công cụ này trong giai đoạn lập hồ sơ và tắt trong các bản phát hành vì chúng thêm tới 20% chi phí cho mỗi lần tái cấu trúc.
Khi phân tích tái cấu trúc, hãy tìm các mẫu tái cấu trúc không cần thiết: một hàm khởi động lại mặc dù UI đầu ra của nó không nên thay đổi. Một nguyên nhân phổ biến là sử dụng lambda không có remember, nơi một đối tượng lambda mới được tạo mỗi lần và Compose coi tham số đã thay đổi. Giải pháp: bọc lambda trong remember { } với các capture cố định.
// Bad: new lambda on every parent recomposition
@Composable
fun Parent() {
Child(onClick = { doSomething() }) // new lambda every time
}
// Good: remember stabilizes the lambda
@Composable
fun Parent() {
val onClick = remember { { doSomething() } }
Child(onClick = onClick) // same reference
}
Câu Hỏi Thường Gặp
Không, tái cấu trúc chỉ là giai đoạn Composition. Sau đó, Layout và Drawing được thực thi. Nếu sau khi tái cấu trúc, kích thước và vị trí của các phần tử không thay đổi, Layout và Drawing có thể được bỏ qua hoàn toàn, tiết kiệm tài nguyên GPU.
Trong hoạt ảnh, tái cấu trúc có thể chạy tới 120 lần mỗi giây (120fps). Đối với tương tác thông thường — 10–60 lần mỗi giây. Điều quan trọng là mỗi lần tái cấu trúc phải nằm trong ngân sách khung hình (8–16 ms), nếu không ứng dụng sẽ bị lag.
Nguyên nhân là thay đổi tham số từ hàm cha. Hàm cha khởi động lại (vì lý do riêng của nó) và truyền một giá trị mới. Để tránh điều này, hãy kiểm tra độ ổn định của tham số và sử dụng remember để ổn định lambda và các giá trị được tính toán.
Không có cách tắt trực tiếp, nhưng có bắt buộc skipping qua readInComposition — State được đọc bên ngoài thân hàm, không đăng ký phụ thuộc. Sử dụng cẩn thận: hàm sẽ không phản ứng với các thay đổi, điều này có thể dẫn đến UI lỗi thời.
Composition đắt hơn vì nó tạo ra tất cả các slot và nút cây từ đầu. Recomposition tái sử dụng các slot hiện có và chỉ cập nhật giá trị của chúng. Trong thực tế, Composition của một màn hình mất 2–10 ms, trong khi tái cấu trúc của một phần tử đơn lẻ mất 0.1–1 ms.
Tổng Kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm