尽管我运用Compose现已有了一段时间的, 但我仍是觉得运用起来束手束脚的. 究其原因, 大概是coding时的思路还没有彻底转换过来, 还没有沉浸在”Compose is Function”之中. 和咱们了解的View不同, 当咱们调用Compose之后, 咱们就失去了它的修正器, 而Compose也只能依照咱们之前设计好的功用去呼应咱们的操作.

除此之外阶段(Phases), 也是一个能够使得你的Compose变的称心如意的进口, 尽管这篇文章不会进行相关介绍, 但我也会在后续的文章中进行介绍.

在了解Side Effects之前, 咱们需求先简单了解一下Lifecycle of composables

Lifecycle of composables

信任咱们对Lifecycle都非常的了解, 在咱们的Android项目开发时, Activity及Fragment的Lifecycle对咱们的功用完结供给了极大的协助. 经过Lifecyle咱们能够很便捷的处理页面不一起期的状况. 幻想一下, 假如将Compose变为一个Activity, 那么许多的功用咱们都能够经过Lifecycle来完结. 当然, Compose的Lifecyle并不如Activity丰厚, 而且设计的思路也不尽相同, 但这都不能阻挠Compose的Lifecycle来协助咱们操控Compose.

如何通过Side Effects来使得你使用Compose变的得心应手?

先看一下官方供给的Lifecycle of composables阐明, 简单来说, Lifecycle of composables包含以下三个部分:

  • 进入组合(创立)
  • 履行 0 次或屡次重组(重组)
  • 退出组合(毁掉)

假如想要了解Side Effects的话, 关于Lifecycle of composables了解到这些就足够了.(写完文章再看这儿, 不, 彻底不行.)

和Activity及Fragment的Lifecycle不同的是, Side Effects并没有Lifecycle这么的爱憎分明, 许多的功用能够用不同的Side Effects来完结, 也使得Side Effects用起来既顺手又疑问.(当然, 这都是现阶段咱们都还在探索中的状况)

下面就让咱们来看一看Side Effects到底是什么, 又有着什么样的功用.

Side Effects

Side Effects有时被译为”副效果”(假如你翻开官方文档, 你会发现左边的列表中仍是副效果), 当然, 大部分状况下都被翻译为”顺便效应.”(个人认为顺便效应是愈加准确的, 究竟假如是副效果的话, 那阐明其效果都是不该该呈现的, 可是作为顺便效应的话, 其效果是否应该呈现, 取决于咱们怎么去操作).

借由官方文档对其的定义和阐明.能够看到以下两个需求关注的当地.

如何通过Side Effects来使得你使用Compose变的得心应手?

一是顺便效应”应从可组合项生命周期的受控环境中调用”, 也便是Lifecycle of composables中调用. 这也是需求先了解Lifecycle of composables的原因.

二是应该”以可猜测的办法履行这些顺便效应”, 这是不该该翻译为副效果的原因, Side Effects的操作应该是可控的, 有用的.

可能在你的了解或实践的进程中, 你会不止一次的疑问, “这个功用我为什么不能直接在OnClick中完结? 他们之中有什么差异?” 这也是我在整个进程中经常疑问的当地, 答案也是很简单, 一切的Side Effects都是处理果的动作, 它们都是由其它因引起的,

说了这么多的定义, 不如来看看这些Side Effects都是怎么经过可组合项生命周期的受控环境中调用来到达以可猜测的办法履行这些顺便效应并协助咱们的Compose变得称心如意.

LaunchedEffect 在某个可组合项的效果域内运转挂起函数

“如需从可组合项内安全调用挂起函数,请运用 LaunchedEffect 可组合项。当 LaunchedEffect 进入组合时,它会发动一个协程,并将代码块作为参数传递。假如 LaunchedEffect 退出组合,协程将撤销。假如运用不同的键重组 LaunchedEffect(请参阅下方的重启效应部分),系统将撤销现有协程,并在新的协程中发动新的挂起函数。”

能够看到LaunchedEffect具有以下特色:

  • 运转suspend functions
  • 进入组合时分履行
  • 退出组合时分撤销
  • 具有重启效应

运转suspend functions

关于suspend functions, 信任咱们都仍是非常了解的, 其间一个非常重要的特色便是, 只能经过suspend functions来调用suspend functions. 下面咱们测验下在不同的方位调用suspend functions的效果.

    //https://gist.github.com/clwater/5454deed3ae258ba3980d260a0ff3299
    suspend fun suspendFunTest() {
        Log.d("clwater", "suspendFunTest Start")
        delay(3000)
        Log.d("clwater", "suspendFunTest Finish")
    }
    @Composable
    fun TestLifecycleCompose() {
        LaunchedEffect(Unit) {
            // 1️⃣ Success
            suspendFunTest()
        }
        // 2️⃣ Error: Suspend function 'suspendFunTest' should be called only from a coroutine or another suspend function    
        suspendFunTest()
        Button(
            onClick = {
            // 3️⃣ Error: Suspend function 'suspendFunTest' should be called only from a coroutine or another suspend function    
            suspendFunTest()
        }) {
            Text(text = "suspendFunTest")
        }
    }

咱们能够看到以上三个方位仅有1️⃣的方位是有用的, 放到2️⃣和3️⃣都会报错. 很明显2️⃣和3️⃣的方位都不能调用suspend functions.

进入组合时分履行

想到咱们前面说到的Lifecycle of composables, 咱们就能够感知到咱们的Composeables进入组合的事情了!

    //https://gist.github.com/clwater/4b9b1bedca4365732de7d8e2c8519190
    @Composable
    fun TestLifecycleCompose() {
        LaunchedEffect(Unit) {
            Log.d("clwater", "TestLifecycleCompose Enter")
        }
        Text(text = "TestLifecycleCompose")
    }

当咱们调用这个Composeables时, 咱们就能够看到以下的log信息了

2023-05-18 16:10:27.432 30584-30584 clwater                 com.clwater.compose_learn_1       D  TestLifecycleCompose Enter

退出组合时分撤销

咱们知道suspend functions都是能够撤销的, 相同LaunchedEffect既能够感知Composeables进入的事情, 也会在Composeables脱离的时分撤销.

    // 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")
            }
        }
    }
    @Composable
    fun TestLifecycleCompose() {
        var show by remember {
            mutableStateOf(true)
        }
        Column {
            Button(onClick = {
                show = false
            }) {
                Text(text = "Hide")
            }
            if (show) {
                ControlCompose()
            }
        }
    }

Log:

2023-06-07 14:12:20.443 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(Unit) out onDispose
2023-06-07 14:12:20.443 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) out onDispose
2023-06-07 14:12:20.503 21111-21111 clwater                 com.clwater.compose_learn_1          D  LaunchedEffect(Unit)
2023-06-07 14:12:20.504 21111-21111 clwater                 com.clwater.compose_learn_1          D  LaunchedEffect(count)
2023-06-07 14:12:27.923 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) in  onDispose
2023-06-07 14:12:27.923 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) out onDispose
2023-06-07 14:12:27.929 21111-21111 clwater                 com.clwater.compose_learn_1          D  LaunchedEffect(count)
2023-06-07 14:12:28.309 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) in  onDispose
2023-06-07 14:12:28.310 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) out onDispose
2023-06-07 14:12:28.314 21111-21111 clwater                 com.clwater.compose_learn_1          D  LaunchedEffect(count)
2023-06-07 14:12:28.608 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) in  onDispose
2023-06-07 14:12:28.609 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) out onDispose
2023-06-07 14:12:28.614 21111-21111 clwater                 com.clwater.compose_learn_1          D  LaunchedEffect(count)
2023-06-07 14:12:32.043 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(count) in  onDispose
2023-06-07 14:12:32.044 21111-21111 clwater                 com.clwater.compose_learn_1          D  DisposableEffect(Unit) in  onDispose

经过Log咱们能够看出, DisposableEffect和LaunchedEffect相同, 在监听统一个值的改变的时分表现根本共同. 可是DisposableEffect却优先于LaunchedEffect触发(这儿的话只能经过Log得到此定论, 得到这个定论的时分我也有点疑问, 可能是Compose state导致性能开支大所以总慢一点? 查找相关文章的时分也没有提及的相关内容.)

当然, 咱们还能够看到LaunchedEffect(Unit)只在进入组合时触发, DisposableEffect(Unit)中的onDispose 也只在退出组合时触发. 假如你想监听组件的Lifecycle, 无妨经过这两个方位来完结.

SideEffect:将 Compose 状况发布为非 Compose 代码

“如需与非 Compose 管理的目标共享 Compose 状况,请运用 SideEffect 可组合项,由于每次成功重组时都会调用该可组合项。”

关于SideEffect, 能够将其了解为非Compose代码的LaunchedEffect(Unit), 而且其在每次重组成功时调用.

其了解和实践运用都有点困难, 尽管其一般被建议在来组合生命周期无关功用中运用. 可是也没有发现在这种状况下的不行代替性.

不过, 从如需与非 Compose 管理的目标共享 Compose 状况这儿, 咱们能够将其了解为能够将SideEffect{}内的元素/功用/代码变为当前效果域下重组的参阅.

咱们先来看一下以下的代码

    @Composable
    fun TestLifecycleCompose() {
        var text by remember { mutableStateOf("Common") }
        Text(text = "text $text")
        Thread.sleep(3 * 1000)
        text = "Delay text"
    }

你认为, 3秒后Text中的内容会改变么? 实践上不会的.

可是当咱们参加SideEffect之后

    @Composable
    fun TestLifecycleCompose() {
        var text by remember { mutableStateOf("Common") }
        Text(text = "text $text")
        SideEffect {
            Thread.sleep(3 * 1000)
            text = "Delay text"
        }
    }

咱们能够发现其在3s后, Text中的内容后发生改变.

(关于发生这样差异的原因, 以下均为我个人了解与想法, 重组这个动作更重视与对调查变量“读”的改变, 没有 SideEffect的时分, Text”读””text”的动作没有改变, 所以不发生重组. 而参加SideEffect后, 将”text”的中”读”的操作进行了触发, 终究引起了重组)

相同的, 咱们在官网中还能够看到

  • produceState:将非 Compose 状况转换为 Compose 状况
  • derivedStateOf:将一个或多个状况目标转换为其他状况
  • snapshotFlow:将 Compose 的 State 转换为 Flow

篇幅有限, 加上学艺不精, 这次就不能过多的介绍了.

跋文

其实在写这篇的文章之前, 我现已把相关的官方文章检查多遍, 还写了一些Demo来协助了解和写文.

不过在实践完结的进程中, 遇到的问题越来越多, 想展现给咱们的也越来越多, 以至于远超估计的内容. 本认为写完此文后, 关于Side Effects不能说是登堂入室, 但也能说的上是略有小成. 但实际的确我发现我的疑问更多了. SideEffect官方文档中是重组时触发, 那么其和LaunchedEffect和DisposableEffect直接的前后联系是怎么样, 它们三个直接这个顺序的原因又到底是为什么? rememberUpdatedState是否不行代替(假如你测验不在子Compose中处理, 彻底能够不运用rememberUpdatedState而到达相同的效果)? 假如不是的话, 其不行代替的运用场景使用是什么姿态?

一起关于Lifecycle of composables重组相关的内容, 在这儿也有了更多的问题. 真可谓牵一发而动全身啊.

也期望后边能够把对这些问题的了解剖析给咱们.

相关参阅文章

官方文档 Lifecycle:https://developer.android.com/jetpack/compose/lifecycle, 写这篇文章的时分这儿的地址官方竟然进行了修正

官方文档 Side Effects:https://developer.android.com/jetpack/compose/side-effects

blog.appcircle.io/article/jet…

medium.com/@callmeryan…

medium.com/mobile-app-…