@Stable
是Jetpack Compose中一个非常重要的注解,它与Compose的重组优化机制密切相关。
@Stable的定义
概念
@Stable
是一个编译时注解,用于标记一个类型的实例在其可观察的公共属性不变的情况下,行为也不会改变。
// Compose中的稳定性定义
@Stable
interface Stable
稳定性的标准
一个类型要被认为是稳定的,必须满足:
- 相同输入产生相同结果:对于两个相等的实例,任何公共方法的调用结果都相同
- 可观察属性变化通知Compose:当任何公共属性发生变化时,Compose运行时会收到通知
- 所有公共属性都是稳定的:类的所有公共属性也必须是稳定类型
@Stable解决的核心问题
问题1:不必要的重组
// ❌ 问题代码:每次都会重组
data class User(
val name: String,
val age: Int,
val timestamp: Long = System.currentTimeMillis() // 不稳定:每次创建都不同
)
@Composable
fun UserProfile(user: User) {
// 即使name和age没变,由于timestamp不同,这个组件总是会重组
Text("${user.name}, ${user.age}")
}
// ✅ 解决方案:使用@Stable
@Stable
data class StableUser(
val name: String,
val age: Int
) {
// 不稳定的属性不暴露给Compose
internal val timestamp: Long = System.currentTimeMillis()
}
问题2:函数参数的稳定性判断
// ❌ 不稳定的lambda
@Composable
fun ParentComponent() {
var count by remember { mutableStateOf(0) }
ChildComponent(
// 每次重组都创建新的lambda,导致ChildComponent总是重组
onClick = { count++ }
)
}
@Composable
fun ChildComponent(onClick: () -> Unit) {
Button(onClick = onClick) {
Text("点击我")
}
}
// ✅ 稳定的lambda
@Composable
fun OptimizedParentComponent() {
var count by remember { mutableStateOf(0) }
// 使用remember缓存lambda
val stableOnClick = remember { { count++ } } // 但这还有闭包问题
// 更好的方案
val stableOnClick = remember {
{ count = count + 1 } // 依然有问题
}
// 最佳方案:使用callback
ChildComponent(
onClick = { count++ } // 如果ChildComponent正确优化,这样就够了
)
}
@Stable的作用机制
1. 编译时优化
// Compose编译器对稳定性的判断
@Stable
class StableClass {
// 编译器知道这个类是稳定的
}
class UnstableClass {
// 编译器会保守地认为这个类是不稳定的
}
@Composable
fun TestComponent(
stable: StableClass, // 参数改变时才重组
unstable: UnstableClass // 每次都可能重组
) {
// 组件内容
}
2. 运行时跳过检查
// 编译器生成的伪代码
@Composable
fun TestComponent(stable: StableClass, unstable: UnstableClass, $composer: Composer) {
if ($composer.changed(stable) || $composer.changed(unstable)) {
// 重组逻辑
} else {
// 跳过重组
}
}
使用场景详解
场景1:数据类
// ✅ 稳定的数据类
@Stable
data class UserInfo(
val id: String,
val name: String,
val email: String
) {
// 计算属性也是稳定的
val displayName: String
get() = name.takeIf { it.isNotBlank() } ?: "Unknown"
}
// ✅ 包含可变状态但通知变化的类
@Stable
class Counter {
private var _value by mutableStateOf(0)
val value: Int get() = _value
fun increment() {
_value++ // 变化会通知Compose
}
}
场景2:函数类型包装器
// 创建稳定的函数包装器
@Stable
class StableCallback<T>(val callback: (T) -> Unit) {
operator fun invoke(value: T) = callback(value)
}
@Composable
fun OptimizedComponent() {
var count by remember { mutableStateOf(0) }
// 包装callback使其稳定
val stableCallback = remember {
StableCallback<Int> { newCount -> count = newCount }
}
ChildComponent(onValueChange = stableCallback)
}
场景3:复杂业务对象
@Stable
class ShoppingCart {
private val _items = mutableStateListOf<CartItem>()
val items: List<CartItem> get() = _items
val totalPrice: Double by derivedStateOf {
_items.sumOf { it.price * it.quantity }
}
fun addItem(item: CartItem) {
_items.add(item) // 变化会自动通知
}
}
@Stable
data class CartItem(
val id: String,
val name: String,
val price: Double,
val quantity: Int
)
场景4:UI状态类
@Stable
data class LoadingState<T>(
val isLoading: Boolean = false,
val data: T? = null,
val error: String? = null
) {
val isSuccess: Boolean get() = data != null && !isLoading
val isError: Boolean get() = error != null && !isLoading
}
@Composable
fun <T> LoadingContent(
state: LoadingState<T>, // 稳定类型,智能重组
onRetry: () -> Unit,
content: @Composable (T) -> Unit
) {
when {
state.isLoading -> CircularProgressIndicator()
state.isError -> ErrorMessage(state.error!!, onRetry)
state.isSuccess -> content(state.data!!)
}
}
带来的优化效果
1. 性能测试对比
// 测试用例:对比稳定性带来的性能提升
@Composable
fun PerformanceTest() {
var counter by remember { mutableStateOf(0) }
// 每秒更新counter
LaunchedEffect(Unit) {
while (true) {
delay(1000)
counter++
}
}
Column {
// 这个会每秒重组(因为counter变化)
Text("Counter: $counter")
// 使用不稳定数据 - 每次都重组
UnstableComponent(UnstableData(System.currentTimeMillis()))
// 使用稳定数据 - 不会重组
StableComponent(StableData("固定内容"))
}
}
data class UnstableData(val timestamp: Long)
@Stable
data class StableData(val content: String)
@Composable
fun UnstableComponent(data: UnstableData) {
// 由于data不稳定,每次父组件重组时都会重组
ExpensiveComponent("不稳定: ${data.timestamp}")
}
@Composable
fun StableComponent(data: StableData) {
// 由于data稳定且内容不变,不会重组
ExpensiveComponent("稳定: ${data.content}")
}
@Composable
fun ExpensiveComponent(text: String) {
// 模拟昂贵的组件
val expensiveValue = remember(text) {
Thread.sleep(100) // 模拟昂贵计算
"计算结果: $text"
}
Text(expensiveValue)
}
2. 内存优化
// 稳定类型减少对象创建
@Stable
class ViewState(
val title: String,
val subtitle: String,
val isVisible: Boolean
) {
// equals和hashCode基于内容
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ViewState) return false
return title == other.title &&
subtitle == other.subtitle &&
isVisible == other.isVisible
}
override fun hashCode(): Int {
return Objects.hash(title, subtitle, isVisible)
}
}
@Composable
fun OptimizedView(state: ViewState) {
// 只有state真正改变时才重组
if (state.isVisible) {
Column {
Text(state.title)
Text(state.subtitle)
}
}
}
为什么@Stable能解决这些问题
1. 编译器优化原理
// 编译器对稳定性的推断
@Composable
fun MyComponent(
stable: String, // 原始类型,稳定
unstable: Any, // 未知类型,不稳定
marked: StableClass // @Stable标记,稳定
) {
// 编译器生成的比较逻辑:
// if (stable != previous_stable ||
// unstable !== previous_unstable || // 引用比较
// marked != previous_marked) {
// // 重组
// }
}
2. 运行时跳过机制
// Compose运行时的跳过逻辑
class ComposerImpl {
fun <T> changed(value: T): Boolean {
val previous = rememberedValue
return if (isStable(T::class)) {
// 稳定类型:使用equals比较
value != previous
} else {
// 不稳定类型:使用引用比较
value !== previous
}
}
}
3. 智能重组决策
@Stable
data class UserProfile(
val name: String,
val avatar: String,
val isOnline: Boolean
)
@Composable
fun UserCard(profile: UserProfile) {
// 由于UserProfile是稳定的,Compose可以:
// 1. 使用equals比较而不是引用比较
// 2. 只有profile内容真正改变时才重组
// 3. 避免不必要的子组件重组
Card {
Row {
Avatar(profile.avatar)
Column {
Text(profile.name)
OnlineIndicator(profile.isOnline)
}
}
}
}
最佳实践指南
1. 何时使用@Stable
// ✅ 适合使用@Stable的场景
// 数据传输对象
@Stable
data class ApiResponse<T>(
val data: T?,
val error: String?,
val isLoading: Boolean
)
// UI状态对象
@Stable
data class FormState(
val name: String = "",
val email: String = "",
val isValid: Boolean = false
)
// 包含Compose状态的业务对象
@Stable
class Repository {
private var _data by mutableStateOf<List<Item>>(emptyList())
val data: List<Item> get() = _data
suspend fun refresh() {
_data = api.getData()
}
}
2. 何时不使用@Stable
// ❌ 不适合使用@Stable的场景
// 包含不稳定属性的类
class UnstableClass {
val timestamp = System.currentTimeMillis() // 每次创建都不同
val random = Random.nextInt() // 不可预测
}
// 行为不一致的类
class InconsistentClass(private var internal: Int) {
fun getValue(): Int {
return internal++ // 每次调用结果不同
}
}
3. 组合使用技巧
// 创建稳定的状态容器
@Stable
class UiStateHolder<T> {
private var _state by mutableStateOf<UiState<T>>(UiState.Loading)
val state: UiState<T> get() = _state
fun updateState(newState: UiState<T>) {
_state = newState
}
}
@Stable
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
// 使用示例
@Composable
fun DataScreen() {
val stateHolder = remember { UiStateHolder<List<String>>() }
LaunchedEffect(Unit) {
try {
val data = fetchData()
stateHolder.updateState(UiState.Success(data))
} catch (e: Exception) {
stateHolder.updateState(UiState.Error(e.message ?: "Unknown error"))
}
}
// 只有state真正改变时才重组
DataContent(state = stateHolder.state)
}
调试稳定性问题
1. 编译器报告
// 在build.gradle中启用稳定性报告
android {
compileOptions {
freeCompilerArgs += [
"-P",
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
project.buildDir.absolutePath + "/compose_reports"
]
}
}
2. 运行时检测
@Composable
fun DebugStability() {
val recompositionCount = remember { mutableStateOf(0) }
SideEffect {
recompositionCount.value++
Log.d("Compose", "Recomposition count: ${recompositionCount.value}")
}
// 组件内容
}
总结
@Stable的核心价值
- 性能优化:减少不必要的重组
- 内存优化:避免重复对象创建
- 可预测性:明确的稳定性契约
- 可维护性:清晰的数据模型定义
解决问题的根本原理
- 编译时推断:帮助编译器做出正确的稳定性判断
- 运行时优化:启用智能的跳过机制
- 类型安全:提供稳定性的编译时保证
记住:@Stable
不是万能的性能优化工具,而是一个契约,告诉Compose编译器和运行时某个类型的行为是可预测和一致的。正确使用它可以显著提升应用性能,但错误使用可能导致意外的行为。