Jetpack Compose中的@Stable和@Immutable注释是什么意思?

44
在研究Jetpack Compose示例项目时,我看到了@Stable@Immutable注释。我一直在查看有关这些注释的Android文档GitHub,但我不理解。

据我所知,如果使用@Immutable,即使状态发生更改,也不应进行重新组合。然而,经过测试,重新组合会继续进行。

@Stable@Immutable注释在Jetpack Compose中具体是做什么?

2个回答

57

定义

@Immutable是一个注解,用于告诉Compose编译器此对象为不可变对象,以便进行优化。如果不使用它,则可能会触发不必要的重新组合。

@Stable是另一个注解,用于告诉Compose编译器此对象可能会更改,但当它更改时,Compose运行时将会被通知。

如果你读到这里还没有理解,那么以下是更详细的说明...


Compose度量报告

当您生成Compose度量报告(https://github.com/androidx/androidx/blob/08c6116/compose/compiler/design/compiler-metrics.md)时,它会标记物品为stableunstable。对于unstable的对象,Compose编译器无法确定对象是否被修改,因此需要重新触发组合。以下是报告的两个片段:

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun SomeClass1(
  stable modifier: Modifier? = @static Companion
)

restartable scheme("[androidx.compose.ui.UiComposable]") fun SomeClass2(
  stable modifier: Modifier? = @static Companion
  stable title: String
  unstable list: List<User>
  stable onClicked: Function1<User>, Unit>
)

可跳过是期望的!

SomeClass1的情况下,它被标记为skippable,因为所有参数都被标记为stable。对于SomeClass2,它没有被标记为skippable,因为它有一个list属性是unstable

当它被标记为skippable时,这是一件好事,因为Compose编译器可以尽可能地跳过重组并且更加优化。

什么情况下无法标记为可跳过?

通常Compose编译器足够聪明,能够推断出什么是stable和什么是unstable。在Compose编译器无法确定稳定性的情况下是可变对象,例如包含var属性的类。

class SomeViewState {
  var isLoading: Boolean
}

如果遇到像Collection这样的类,例如List,即使接口为看起来不可变的List,它实际上可能是可变列表,那么就会失败地决定稳定性。 例如:

data class SomeViewState {
    val list: List<String>
}
@Composable
fun ShowSomething(data: SomeViewState) {
}
尽管上面的可组合部件接受所有属性为valSomeViewState,但它仍然是不稳定的。你可能会想为什么?那是因为在使用方面,你实际上可以像这样使用它:MutableList
ShowSomething(SomeViewState(mutableListOf()))

因此,编译器将不得不将其标记为不稳定

因此,像这样的情况下,我们想要实现的目标是使它们重新变得稳定,以便进行优化。


@Stable和@Immutable

有两种方法可以使其重新变得稳定,分别是使用@Stable@Immutable

使用@Stable,如上所述,它意味着该值可以更改,但当它发生更改时,我们必须通知Compose编译器。完成方式是通过使用mutableStateOf()

@Stable
class SomeViewState {
  var someFlag by mutableStateOf(false)
}

使用 @Immutable,这意味着当您传递给 Composable 时,您将始终创建数据的新副本,换句话说,您承诺您的数据是不可变的。从上面的例子:

@Immutable
data class SomeViewState {
    val list: List<String>
}
@Composable
fun ShowSomething(data: SomeViewState) {
}

在使用@Immutable进行注释后,在使用端,您应该确保创建一个新的列表而不是直接更改原始列表。

示例:

class ViewModel {
    val state: SomeViewState = SomeViewState(listOf())
    fun removeLastItem() {
        val newList = state.list.toMutableList().apply {
                removeLast()
            }
        state = state.copy(
            list = newList
        )
    }
}

例子:不要这样做。

class ViewModel {
    val state: SomeViewState = SomeViewState(mutableListOf())
    fun removeLastItem() {
        state.list.removeLast() // <=== you violate your promise of @Immutable!
    }
}

欲深入了解,可查阅以下链接:


2
这是一个完美的解释。 - Hawklike
1
比起被接受的答案,这个解释好得多(或者更深入)。做得好!谢谢。 - StefMa

40

编译器将两者视为相同,但

  • 使用@Immutable表示该值永远不会改变。
  • 使用@Stable表示该值是可观测的,如果它发生了改变,则会通知监听器。

2
用这些注释来注释一个类有什么好处? - Jim Ovejera
9
他们让Compose编译器进行一些优化,使得重新组合逻辑更小更快。 - Ted Hopp
使用@Stable时,需要使用mutableStateOf吗? - hamid Mahmoodi
1
@Farid - 当状态没有改变时,组合会跳过可组合项。有时它可以自行推断事物是否发生了变化。注释@Immutable@Stable可以帮助对象避免触发重新组合。例如,请参见https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8或https://www.jetpackcompose.app/articles/how-can-I-debug-recompositions-in-jetpack-compose。 - Ted Hopp
1
为什么,例如,Color被标记为@Stable而不是@Immutable - Mark
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接