Jetpack Compose中的副作用和效果处理
副作用是指任何逃出函数范围的东西。在Jetpack Compose中,它指的是一个可组合函数内部的内容。
为了处理这样的效果,我们使用一个EffectHandler ,这是一个安全的环境。
前提条件
要跟上本教程,读者应该具备以下条件。
- 对
Kotlin编程语言的理解。 - 对[Jetpack Compose]和[可组合生命周期]有基本的了解和经验。
目标
在本教程结束时,读者将能够。
- 理解副作用,以及它们是如何发生的。
- 创建无副作用的可组合程序。
简介
效果应该从一个知道可合成物生命周期的受控环境中调用。需要这些组件来修改应用程序的状态。
当你需要修改可组合物的状态时,就可以使用效应API,这样就可以预见地执行副作用。
让我们开始吧,看看这是如何发生的。
副作用和它们是如何发生的
正如所讨论的,副作用是存在于可组合函数范围之外的任何东西。
副作用会对一个应用程序造成不利影响。这是因为它们可以在可组合函数的范围之外修改应用程序的状态。
这意味着每次调用可组合函数时,它的反应都可能不同。
可组合的函数会运行数次,甚至跳过执行。因此,依赖它们的全局状态可能会导致意外的行为。
Compose有几种处理副作用的策略。这些技术协助管理副作用的寿命,并在不再需要它们后将其删除。
我们有几种方法可以在 Compose 中进行副作用处理。
效应处理程序
要理解效果处理程序,我们首先需要了解Compose 生命周期。
可组合物在屏幕上渲染时加入组合物,当它们从用户界面树上删除时离开。在某些情况下,效果可能从一个事件跨越到另一个事件。
效果可能有较长的生命周期,而其他效果的生命周期则较短。这使得你可以在许多构图中利用它们。
效果处理程序可以分为两种。
- 暂停的效果(SuspendedEffect
- 非暂停的副作用
暂停的效果
这些是当我们在组合体内部进行长期运行的操作(如网络调用)时可能出现的效果。
这种效应中流行的技术是rememberCoroutineScope 和launchedEffect 。
启动效应(LaunchedEffect
LaunchedEffect 可以用来在一个可组合的生命周期内做任务。如果composable不再显示在屏幕上,这个coroutine将自毁。这有助于避免内存泄漏。
一般来说,lauchedEffect ,做以下事情。
- 当一个效果进入合成时,
launchedEffect执行它,并在它退出时清除它。 - 当按键发生变化时,效果会被终止并重新启动。
- 我们也可以用它来扩展到组合中的一个任务上。
让我们以一个定时器为例。我们希望这个定时器在一个可组合的执行开始时启动,在组合结束时停止。
在这种情况下,我们在LaunchedEffect 块内启动定时器。当组合体退出时,我们不必为暂停或清理定时器相关的代码而烦恼。
@Composable
fun Timer() {
LaunchedEffect(key1 = Unit, block = {
try {
initTimer(2000) {
Toast.makeText(this@MainActivity, "The timer ended", Toast.LENGTH_SHORT).show()
}
} catch(e: Exception) {
Toast.makeText(this@MainActivity, "The timer was cancelled: $e", Toast.LENGTH_SHORT).show()
}
})
}
suspend fun initTimer(time: Long, onEnd: () -> Unit) {
delay(timeMillis = time)
onEnd()
}
在上述代码的LaunchedEffect 部分中,initTimer() 函数将在时间完成时,或在终止时显示一个祝酒信息。
LaunchedEffect 块接受key1 和block 参数。当第一个参数key1 的值发生变化时,会通知launchedEffect ,重新启动循环程序并销毁旧的循环程序。
第二个参数是block 。它是一个lambda,当launchedEffect 被调用时被执行。Suspend函数在这个块内执行。
RememberCoroutineScope
在一些特殊情况下,由于以下原因,LaunchedEffect 不被使用。
- 一个
launchedEffect是可以自己组成的。因此,它总是以其他一些可组合的函数开始。 - 使用
launchedEffect限制了对循环程序生命周期的控制。没有办法显式地终止该轮回程序。这是因为它是根据可组合生命周期开始和结束的。
由于这些限制,在这种情况下,rememberCoroutineScope 是首选。
rememberCoroutineScope通常返回一个作用域。这个方法产生一个CoroutineScope,可以用来构造作为组合的一部分的任务。
rememberCoroutineScope 允许我们从任何组合物或回调中启动coroutines。这可以做到,而不必担心coroutine的寿命。
@Composable
fun Timer() {
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
Toast.makeText(this@MainActivity, "The timer Started", Toast.LENGTH_SHORT).show()
coroutineScope.launch {
try {
initTimer(2000) {
Toast.makeText(this@MainActivity, "The timer ended", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Toast.makeText(this@MainActivity, "The timer was cancelled: $e", Toast.LENGTH_SHORT).show()
}
}
}) {
Text("Start")
}
}
suspend fun initTimer(time: Long, Finish : () -> Unit){
delay(timeMillis = time)
Finish()
}
上述代码中的rememberCoroutineScope() 方法是用来访问范围的。值得注意的是,范围与可组合程序的寿命有关。
所以,如果在这个可组合的退出时,有任何一个coroutine正在执行,它将被立即停止。
当按钮被点击时,coroutine被启动。这将在2秒内启动定时器。
一个烤面包会出现,表明计时器已经开始。2秒后,会出现另一个祝酒词,表示分配的时间已经结束。
LaunchedEffect与 不同的是,它被用来对由组合体启动的任务进行范围控制。rememberCoroutineScope
rememberCoroutineScopeWWW.STEELT.COM另一方面,管理由用户交互启动的任务。
非暂停效应
在非暂停效应中,我们可以启动一个副效应来初始化一个回调。这是在组合初始化时完成的。在构成完成后,它被销毁。
在非悬浮式的副作用下,我们将看看DisposableEffect 和SideEffect 。
DisposableEffect
DisposableEffect 是用于一旦按键改变或可组合物离开组合物时需要修复的副作用。
在这样的情况下,DisposableEffect 键的更新,可组合的必须能够破坏其当前的效果。这之后将再次调用效果来重新启动。
DisposableEffect 是用来消除非暂停的效果。当一个可组合的开始和可组合的键更新时,它就开始了。
在最后抛出一个回调是必要的。当可组合体离开组合体时,它被销毁。
当它们的键在每次重组中更新时,这也会发生。在这种情况下,该效果将被处置并重新启动。
假设我们有一个可组合的函数来处理on-back pressed动作,如下所示。
@Composable
fun MyComposable(backPressedDispatcher: OnBackPressedDispatcher) {
val callback = remember {
object:OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// Perform some actions
}
}
}
DisposableEffect(backPressedDispatcher) { // restart if dispatcher changes
backPressedDispatcher.addCallback(callback) //attach the call back here
onDispose {
callback.remove() // this prevents memory leaks
}
}
}
在这个例子中,当设备的后退按钮被按下时,调度器被调用。我们的DisposableEffect 块将处理后按下的调度器的回调。
我们打算在可组合开始组合时,以及在调度器更新时附加回调。
调度器被用作效果处理键来完成这一功能。在这种情况下,效果将被处理掉并重新引入。
如果你只想让效果在加入组合时执行一次,然后在你离开时处置它,你可以提供一个常量作为键。这可以通过:DisposableEffect(true)或DisposableEffect(Unit) 来实现。
值得注意的是,
DisposableEffect总是需要使用至少一个键。
侧面效应
这是最简单的副作用处理程序。它允许我们访问一个代码块,这个代码块在每次成功组合后都会被执行。
var i = 0
@Composable
fun MyComposable(){
i++ // incrementing this variable here will cause a side-effect
Button(onClick = {}){
Text(text = "Click")
}
}
在上面的代码中,我们假设我们有一个全局变量i ,我们需要在我们的可组合函数中递增。在可组合函数中递增i ,将引起一个副作用。
变量i 的值在重新组合后将不符合预期。如果你需要在可组合函数中进行网络调用来更新用户界面,而不提供一个安全的环境来处理这个副作用,那么情况就会更糟。
处理当你增加变量i 时可能发生的副作用的最好方法是将代码包裹在一个副作用块中,如下所示。
var i = 0
@Composable
fun MyComposable(){
SideEffect { //this will handle the side effect that may occur
i++
}
Button(onClick = {}){
Text(text = "Click")
}
}
SideEffect 块内的代码将在组合成功后被执行。如果组合失败,代码将不会被执行。
总结
在Jetpack Compose应用中,你可能会遇到你不打算在Composable 中处理的副作用。
在某些时候,我们可能希望它与可组合的生命周期相联系。我们要确保它在正确的生命周期阶段执行。
我们还确保它被停止以避免内存泄漏,并在CoroutineContext 。这个上下文是由效果处理程序提供的。