@Stable 和 @Immutable 注释在 Jetpack Compose 中意味着什么?

Ji *_*bin 51 android android-jetpack-compose compose-recomposition

在研究 Jetpack Compose 示例项目时,我看到了@Stable注释@Immutable。我一直在浏览Android 文档GitHub关于这些注释的信息,但我不明白。

据我了解,如果使用@Immutable,即使状态改变,也不应该发生重组。然而,作为测试的结果,重组仍在继续。

@StableJetpack Compose 中注释的具体作用是什么@Immutable

I'm*_*gon 74

定义

@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>
)
Run Code Online (Sandbox Code Playgroud)

需要可跳过!

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

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

什么时候会无法标记为可跳过?

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

class SomeViewState {
  var isLoading: Boolean
}
Run Code Online (Sandbox Code Playgroud)

另一种无法确定稳定性的情况是像 之类的类CollectionList因为即使接口List看起来不可变,它实际上也可以是一个可变列表。例子:

data class SomeViewState {
    val list: List<String>
}
@Composable
fun ShowSomething(data: SomeViewState) {
}
Run Code Online (Sandbox Code Playgroud)

尽管上面的 Composable 接受SomeViewState其所有属性所在的位置val,但它仍然是unstable。你可能想知道为什么?这是因为在使用方面,您实际上可以将它与 a 一起使用MutableList,如下所示:

ShowSomething(SomeViewState(mutableListOf()))
Run Code Online (Sandbox Code Playgroud)

因此,编译器必须将其标记为unstable.

所以在这样的情况下,我们想要实现的就是stable重新制作它们,从而优化它们。


@Stable 和 @Immutable

有两种方法可以stable重新制作,分别是使用@Stable@Immutable

使用@Stable,如上所述,意味着该值可以更改,但是当它确实更改时,我们必须通知 Compose 编译器。做到这一点的方法是通过使用mutableStateOf()

@Stable
class SomeViewState {
  var isLoading by mutableStateOf(false)
}
Run Code Online (Sandbox Code Playgroud)

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

@Immutable
data class SomeViewState {
    val list: List<String>
}
@Composable
fun ShowSomething(data: SomeViewState) {
}
Run Code Online (Sandbox Code Playgroud)

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

示例:

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

示例不要:

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

为了更深入的了解,您可以阅读以下链接:

  • 这是一个完美的解释。 (2认同)

Gab*_*tti 43

编译器对两者的处理方式相同,但是

  • 使用@Immutable是一种价值永远不会改变的承诺。
  • using@Stable是一个承诺,即该值是可观察的,如果它确实发生变化,侦听器会收到通知。

  • @JimOvejera - 他们让 compose 编译器进行一些优化,从而产生更小、更快的重组逻辑。 (9认同)
  • 用这些注释类有什么好处? (2认同)
  • 例如,为什么“Color”被标记为“@Stable”而不是“@Immutable”? (2认同)