网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
// https://gist.github.com/clwater/440af58717e699d4963f915f520d494a
@Composable
fun TestLifecycleCompose() {
var isShow by remember {
mutableStateOf(true)
}
Column {
if (isShow) {
TestLifecycleComposeText()
}
Button(
onClick = { isShow = !isShow }
) {
Text(text = "TestLifecycleCompose show: $isShow")
}
}
}
@Composable
fun TestLifecycleComposeText() {
LaunchedEffect(Unit) {
Log.d("clwater", "TestLifecycleCompose Enter")
try {
delay(10 * 1000)
Log.d("clwater", "TestLifecycleCompose Finish")
} catch (e: Exception) {
Log.d("clwater", "TestLifecycleCompose Error: $e")
}
}
Text(text = "TestLifecycleCompose")
}
如果我们启动后不做任何操作, 或者超过10s后再次点击, 我们会看到如下的log
2023-05-18 16:15:27.449 30584-30584 clwater com.clwater.compose_learn_1 D TestLifecycleCompose Enter
2023-05-18 16:15:37.453 30584-30584 clwater com.clwater.compose_learn_1 D TestLifecycleCompose Finish
但是当我们在10s再次点击按钮使得上面的Composeables不在显示的时候, 我们就可以看到出现了以下的log
2023-05-18 16:22:03.698 31930-31930 clwater com.clwater.compose_learn_1 D TestLifecycleCompose Enter
2023-05-18 16:22:04.895 31930-31930 clwater com.clwater.compose_learn_1 D TestLifecycleCompose Error: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@67197cb
我们可以很清除的看到我们在LaunchedEffect运行的内容, 因为对应Composeables离开而被取消.
重启效应
“Compose 中有一些效应(如 LaunchedEffect、produceState 或 DisposableEffect)会采用可变数量的参数和键来取消运行效应,并使用新的键启动一个新的效应。”
重启效应不单单只有LaunchedEffect具有, 但是相关的效果都表现一致, 这里我们针对LaunchedEffect的重启效应进行详细分析.
值得注意的是, 当我们通过重启效应来启动新的效应的时候, 我们旧的效应(同一个键)会被取消. 类似前面退出组合时候取消的效果.
@Composable
fun TestLifecycleCompose() {
var clickCount by remember {
mutableStateOf(0)
}
LaunchedEffect(clickCount) {
Log.d("clwater", "TestLifecycleCompose clickCount: $clickCount")
}
Column {
Button(onClick = { clickCount++ }) {
Text("clickCount $clickCount")
}
}
}
当我们点击按钮的时候, 我们可以在日志中看到如下的输出.当然, 你可以通过在onClick中打印这些内容来实现同样的功能. 不过这两种实现的方式侧重点是不同的. 在onClick中, 你侧重的内容是点击按钮, 而在LaunchedEffect中, 你侧重的是clickCount值的变化.
2023-05-19 10:21:18.864 30841-30841 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 0
2023-05-19 10:21:26.114 30841-30841 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 1
2023-05-19 10:21:26.387 30841-30841 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 2
2023-05-19 10:21:26.594 30841-30841 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 3
2023-05-19 10:21:26.806 30841-30841 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 4
2023-05-19 10:21:27.005 30841-30841 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 5
简单的, 如果我们增加对取消时的异常捕获, 我们就能看到下面类似的log
@Composable
fun TestLifecycleCompose() {
var clickCount by remember {
mutableStateOf(0)
}
LaunchedEffect(clickCount) {
try {
Log.d("clwater", "TestLifecycleCompose clickCount: $clickCount")
delay(1 * 1000)
Log.d("clwater", "TestLifecycleCompose clickCount: $clickCount finish")
} catch (e: Exception) {
Log.d("clwater", "TestLifecycleCompose Error: $e")
}
}
Column {
Button(onClick = { clickCount++ }) {
Text("clickCount $clickCount")
}
}
}
2023-06-05 14:30:38.948 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 0
2023-06-05 14:30:39.951 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 0 finish
2023-06-05 14:30:46.362 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 1
2023-06-05 14:30:47.363 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 1 finish
2023-06-05 14:30:47.432 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 2
2023-06-05 14:30:47.597 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose Error: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@ac45c38
2023-06-05 14:30:47.597 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 3
2023-06-05 14:30:47.776 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose Error: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@ed9b9fe
2023-06-05 14:30:47.776 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 4
2023-06-05 14:30:47.965 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose Error: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@d0a879d
2023-06-05 14:30:47.965 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 5
2023-06-05 14:30:48.968 17377-17377 clwater com.clwater.compose_learn_1 D TestLifecycleCompose clickCount: 5 finish
rememberCoroutineScope:获取组合感知作用域,以便在可组合项外启动协程
"由于 LaunchedEffect 是可组合函数,因此只能在其他可组合函数中使用。为了在可组合项外启动协程,但存在作用域限制,以便协程在退出组合后自动取消,请使用 rememberCoroutineScope。 此外,如果您需要手动控制一个或多个协程的生命周期,请使用 rememberCoroutineScope,例如在用户事件发生时取消动画。
rememberCoroutineScope 是一个可组合函数,会返回一个 CoroutineScope,该 CoroutineScope 绑定到调用它的组合点。调用退出组合后,作用域将取消。"
可以看到rememberCoroutineScope具有以下特点:
- 在composable之外启动协程
- 可手动控制一个或多个协程的生命周期
- 是一个composable function
在composable之外启动协程
最核心的作用就是这个, 在composable之外启动协程, 我们还是尝试在不同的部分来启动一个suspend functions.
@Composable
fun TestLifecycleCompose() {
// 1️⃣ Error: Suspend function 'suspendFunTest' should be called only from a coroutine or another suspend function
suspendFunTest()
val scope = rememberCoroutineScope()
Button(onClick = {
// 2️⃣ Error: Suspend function 'suspendFunTest' should be called only from a coroutine or another suspend function
suspendFunTest()
scope.launch {
// 3️⃣ Success
suspendFunTest()
}
}) {
Text(text = "suspendFunTest")
}
}
和前面的例子类似的, 在位置1️⃣和2️⃣都会报错, 仅在3️⃣的位置才能正常使用. 当我通过手动触发某些协程的时候, 这个方法就变得十分的好用. 比如在官方说明中点击某个按钮后再Scaffold中调用showSnackbar
@Composable
fun MoviesScreen(scaffoldState: ScaffoldState = rememberScaffoldState()) {
// Creates a CoroutineScope bound to the MoviesScreen's lifecycle
val scope = rememberCoroutineScope()
Scaffold(scaffoldState = scaffoldState) {
Column {
/* ... */
Button(
onClick = {
// Create a new coroutine in the event handler to show a snackbar
scope.launch {
scaffoldState.snackbarHostState.showSnackbar("Something happened!")
}
}
) {
Text("Press me")
}
}
}
}
可手动控制一个或多个协程的生命周期/是一个composable function
前言中我们有提及, 不论是哪种Side Effects, 都是某种来处理果的过程, 但是实际的开发过程中, 我们不可避免的需要更加精细的对协程进行控制, 虽然前面提及到的点有两个, 但是我认为这两个放在一个例子中可以更好的帮助大家进行理解.
@Composable
fun TestLifecycleCompose() {
var showChild by remember {
mutableStateOf(true)
}
var showParent by remember {
mutableStateOf(true)
}
val scope = rememberCoroutineScope()
Column {
Row {
Button(onClick = {
showParent = false
}) {
Text(text = "Hide Parent")
}
Button(onClick = {
showChild = false
}) {
Text(text = "Hide Child")
}
}
if (showParent) {
Button(onClick = {
scope.launch {
try {
Log.d("clwater", "TestLifecycleCompose")
delay(1000 * 1000)
} catch (e: Exception) {
Log.d("clwater", "TestLifecycleCompose Error: $e")
}
}
}) {
Text(text = "Parent")
}
}
if (showChild) {
TestLifecycleComposeChild()
}
}
}
@Composable
fun TestLifecycleComposeChild() {
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
try {
Log.d("clwater", "TestLifecycleComposeChild")
delay(1000 * 1000)
} catch (e: Exception) {
Log.d("clwater", "TestLifecycleComposeChild Error: $e")
}
}
}) {
Text(text = "Child")
}
}
通过log我们可以看到, 不论先关闭哪一个按钮, log的结果都是一样的(子composables被取消), 因为rememberCoroutineScope返回的CoroutineScope被绑定到调用它的组合点, 所以虽然看起来TestLifecycleCompose没有内容被显示, 但是这个function还没有退出, 所以调用的协程还一直在执行, 而TestLifecycleComposeChild却完完全全的被执行退出, 所以绑定在TestLifecycleComposeChild的scope就会被取消.
2023-06-05 16:31:28.529 22058-22058 clwater com.clwater.compose_learn_1 D TestLifecycleCompose
2023-06-05 16:31:29.341 22058-22058 clwater com.clwater.compose_learn_1 D TestLifecycleComposeChild
2023-06-05 16:31:34.309 22058-22058 clwater com.clwater.compose_learn_1 D TestLifecycleComposeChild Error: kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@2fc6b1d
2023-06-05 16:31:53.457 22151-22151 clwater com.clwater.compose_learn_1 D TestLifecycleCompose
2023-06-05 16:31:53.936 22151-22151 clwater com.clwater.compose_learn_1 D TestLifecycleComposeChild
2023-06-05 16:31:58.985 22151-22151 clwater com.clwater.compose_learn_1 D TestLifecycleComposeChild Error: kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@8d47f01
rememberUpdatedState:在效应中引用某个值,该效应在值改变时不应重启
“当其中一个键参数发生变化时,LaunchedEffect 会重启。不过,在某些情况下,您可能希望在效应中捕获某个值,但如果该值发生变化,您不希望效应重启。为此,需要使用 rememberUpdatedState 来创建对可捕获和更新的该值的引用。这种方法对于包含长期操作的效应十分有用,因为重新创建和重启这些操作可能代价高昂或令人望而却步。”
说实话, 第一次看到这个解释的时候我感觉我更加的不理解了, 不过从官方的介绍中, 我们可以看到rememberUpdatedState解决的主要问题是在效应中捕获某个值,不希望效应重启.
这里的我们之间看一下示例代码,
https://gist.github.com/clwater/214e28128f93d8e491afd189618fea36
@Composable
fun DelayCompose(click: Int) {
val rememberClick = rememberUpdatedState(newValue = click)
LaunchedEffect(Unit) {
delay(5000)
Log.d("clwater", "TestLifecycleCompose click: $click")
Log.d("clwater", "TestLifecycleCompose rememberClick: ${rememberClick.value}")
}
}
@Composable
fun TestLifecycleCompose() {
var lastClick by remember {
mutableStateOf(-1)
}
Column {
Button(onClick = {
Log.d("clwater", "onClick 0")
lastClick = 0
}) {
Text(text = "0")
}
Button(onClick = {
Log.d("clwater", "onClick 1")
lastClick = 1
}) {
Text(text = "1")
}
}
DelayCompose(click = lastClick)
}
代码还是比较简单的, 我们分别尝试先点击两次"按钮0", 后点击两次"按钮1", 已经先点击两次"按钮1", 后点击两次"按钮0". 我们先预测下最后的log中会是什么样子的, 再来看看实际上log的情况.
2023-06-07 13:23:21.551 6400-6400 clwater com.clwater.compose_learn_1 D onClick 0
2023-06-07 13:23:21.826 6400-6400 clwater com.clwater.compose_learn_1 D onClick 0
2023-06-07 13:23:22.314 6400-6400 clwater com.clwater.compose_learn_1 D onClick 1
2023-06-07 13:23:22.649 6400-6400 clwater com.clwater.compose_learn_1 D onClick 1
2023-06-07 13:23:24.415 6400-6400 clwater com.clwater.compose_learn_1 D TestLifecycleCompose click: -1
2023-06-07 13:23:24.415 6400-6400 clwater com.clwater.compose_learn_1 D TestLifecycleCompose rememberClick: 1
2023-06-07 13:23:42.094 6711-6711 clwater com.clwater.compose_learn_1 D onClick 1
2023-06-07 13:23:42.462 6711-6711 clwater com.clwater.compose_learn_1 D onClick 1
2023-06-07 13:23:42.960 6711-6711 clwater com.clwater.compose_learn_1 D onClick 0
2023-06-07 13:23:43.198 6711-6711 clwater com.clwater.compose_learn_1 D onClick 0
2023-06-07 13:23:46.236 6711-6711 clwater com.clwater.compose_learn_1 D TestLifecycleCompose click: -1
2023-06-07 13:23:46.236 6711-6711 clwater com.clwater.compose_learn_1 D TestLifecycleCompose rememberClick: 0
我们可以看到, 没有通过rememberUpdatedState获得值是默认值, 也可以理解为我们首次调用时传入的值, 即使DelayCompose方法没有做任何修饰, 并且入参的内容都不一样. (个人理解是由于Compose对重启的优化, 避免页面被重新绘制多次没有变化的元素), 实际上我们的DelayCompose实际上只执行了一次(你可以在DelayCompose的LaunchedEffect中添加Log来认证一下).
再回来看我们的代码, DelayCompose这个方法实际上在-1入参后就进入了延时, 并不对新的入参做反应了, 这也就导致了我们Log中输出入参信息只有-1, 但通过rememberUpdatedState获取的值, 他会在使用的时候更新当前新的值, 也是的我们在Log中可以看到我们实际点击的情况.
DisposableEffect:需要清理的效应
“对于需要在键发生变化或可组合项退出组合后进行清理的附带效应,请使用 DisposableEffect。如果 DisposableEffect 键发生变化,可组合项需要处理(执行清理操作)其当前效应,并通过再次调用效应进行重置。”
简单来说, 当我们退出组合时会触发DisposableEffect, 我们可以在这里解绑或注销一些资源. 需要注意的是在DisposeableEffect中是无法直接调用suspend functions的, 也就是说DisoposeableEffect并不属于Compose state. 使用起来的话代码也比较简单.
fun TestLifecycleCompose(obsever: TestObserver) {
LaunchedEffect(Unit){
obsever.start()
}
DisposableEffect(Unit) {
onDispose {
obsever.stop()
}
}
}
上述只是一个最简单的使用, 同时我们主要到, DisposeableEffect也有一个key1, 那么它和LaunchedEffect直接的区别是什么? 还是说DisposeableEffect只是LaunchedEffect加一个onDispose并且不能调用suspend functions的修改版本?
我们试一下以下代码, 并在屏幕中点击按钮, 我们不妨先想象一下最终的Log是什么样子的.
@Composable
fun ControlCompose() {
LaunchedEffect(Unit) {
Log.d("clwater", "LaunchedEffect(Unit)")
}
DisposableEffect(Unit) {
Log.d("clwater", "DisposableEffect(Unit) out onDispose")
onDispose {
Log.d("clwater", "DisposableEffect(Unit) in onDispose")
}
}
var count by remember {
mutableStateOf(0)
}
Button(onClick = { count++ }) {
Text(text = "count $count")
}
LaunchedEffect(count) {
Log.d("clwater", "LaunchedEffect(count)")
}
DisposableEffect(count) {
Log.d("clwater", "DisposableEffect(count) out onDispose")
onDispose {
Log.d("clwater", "DisposableEffect(count) in onDispose")
}
}
![img](https://img-blog.csdnimg.cn/img_convert/93cbfe5ce28063b1b4138c78067d7dd2.png)
![img](https://img-blog.csdnimg.cn/img_convert/db9682369692a4695c1674367489d3b4.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
ter", "DisposableEffect(count) out onDispose")
onDispose {
Log.d("clwater", "DisposableEffect(count) in onDispose")
}
}
[外链图片转存中...(img-0pfihzXM-1715731406605)]
[外链图片转存中...(img-XWyig43C-1715731406605)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**