生命周期和附带效应

可组合函数应该是无副作用的,但是当需要改变应用的状态时, 可组合函数需要从受控的环境调用,这个环境能够感知可组合函数的生命周。在这篇文章中,你会学习到可组合函数的生命周期与JetPack Compose提供的不同的副作用API。

可组合函数的生命周期

一个组合描述了App的UI界面,通过运行可组合函数生成。一个组合就是一个由可组合函数构成的树状结构,这个结构对应与UI界面。
当Jetpack Compose 框架第一次运行可组合函数时,在初始化组合期间,框架会追踪你调用的可组合函数。当APP的状态发生改变,Jetpack Compose 框架会安排一个重组。重组就是当JC框架重新执行可组合函数来响应状态改变,然后更新组合来反应任何的改变。
一个组合可以由一个初始组合产生,并且会
合成只能通过初始合成来生成,并且只能通过重组来更新。 修改合成的唯一方法是通过重组。


image.png

重组的一般触发条件就是StateM<T>状态的改变,Compose 会追踪组合中的这些状态,并且运行与其相关的可组合函数,和任何不可跳过的可组合函数。

注意:
可组合函数的生命周期与View、activity、fragment的生命周期相似,当一个可组合函数需要与外部资源进行交互,那么就会产生比较复杂的生命周期,那么你需要使用effcts

如果一个组合函数被调用多次,那么组合中就会包含多个实例。在组合中,每个调用都有自己的生命周期。

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

这个图代表了MyComposable这个UI组合。不同的颜色代表不同的实例。

组合中的可组合函数剖析

组合中的一个可组合函数实例通过调用入口(call site)标识,Compose框架编译器认为每个调用入口都是不同的。在组合中,从多个调用入口调用可组合函数将会创建多个可组合函数的实例。

关键词:调用入口(call site)的意思是源码中可组合函数(composable)被调用的位置,调用位置会影响可组合函数在组合中的位置,同理也会影响UI树的结构。

如果在一次重组期间,一个可组合函数调用与上次重组不同的可组合函数,Compose框架将会识别哪个可组合函数被调用或没有被调用,也会标识那些在两次组合中都调用的组合函数,如果都被调用的函数的输入并没有发生变化,Compose框架会避免去重新组合他们

保存标识(Preserving identity)对于将副作用(side effects )和他们的可组合函数关联起来至关重要,其目的是不用每次重组都重新调用一遍重组函数。
以下面的例子为例

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

在上面的代码片段中,LoginScreen将会有条件的去调用LoginError可组合函数,并且会一直调用LoginInput重组函数。每个调用都有唯一的调用入口和源码位置,编译器将会利用他们去做唯一标识。


这个图表示,当组合因为状态改变发生重组,LoginScreen没有发生重组,同一个颜色代表同一个实例。
尽管LoginInput在初始化和状态改变后都被调用了,但初始化生成的LoginInput实例在重组期间都不会发生改变,因为LoginInput并没有任何参数发生了变化,重组时LoginInput重组函数的调用将会被Compose跳过。

添加额外的信息帮助更智能的重组

多次调用可组合函数会降可组合函数多次加入到组合中,当从同一个调用入口多次调用可组合函数,Compose没有任何信息来唯一标识每次调用的可组合函数,所以除了调用入口,执行顺序就会被用来作为唯一标识。有时需要这种操作,但在某些情况下这可能导致不想要的行为。

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}

在上面的例子中,Compose使用执行顺序加调用入口来区分组合中不同的实例。如果一个新的movie被添加到列表的底部,Compose框架可以复用组合中已经存在的实例,因为他们的位置在列表中没有改变。


上图表示插入一个新的movie到列表中,相同的颜色代表相同的实例,也是就重新组合后以前的实例没有发生改变。
但是,如果movies列表发生了改变,无论是在在顶部,还是中间插入数据,或者移除、重新排序数据,都会导致整个MovieOverview的重组。这种情况是必须要注意的,例如如果MovieOverview正在通过副作用获取movie的图片,这个时候如果重组发生了,加载图片的动作讲过被取消然后重新开始。

@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}

当在列表顶部插入一条数据,MovieOverview可组合函数不能被复用,所有的副作用将重启。不同的颜色代表不同的实例。
理想情况下,我们想将MovieOverview实例的标识与传入的movie的标识联系起来。如果我们重拍下movies列表,理想情况下,我们会重排序组合树中的实例,并不是用不同的实例去重新组合。Compose提供了一种由开发人员自己决定使用什么作为唯一标识的方法,key 可重组函数。
通过调用key 可组合函数包裹一个代码块,来传入一个或多个值。这些值将被混合使用作为组合中实例的唯一标识。一个key的值不需要全局唯一,只需要在可组合函数的调用入口范围内唯一。所以,在这个例子中,每个movie需要一个key来标识movies。

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

如上面代码所示,即使列表中的元素发送了变化,Compose也可以识别出那些变化的,并可以复用没有变化的实例。



标识一个新的元素插入到列表中,Compose可以复用movie.id没有变化的,而且他们的副作用还是可以继续执行。

关键:
使用key可重组函数帮助Compose标识组合中的可重组实例,这对于多个可重组函数在同一个调用入口调用,并且还包含副作用或者内部状态是重要的。

一些可组合函数内置支持key可组合函数,例如LazyColumn。

@Composable
fun MoviesScreen(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

如果输入没有变化就跳过

如果一个可组合函数已经在组合中,当所有的输入都是稳定的并且没有改变,这个可组合函数将跳过重组。
一个稳定的类型必须满足如下的条件:

  • 对于两个相同的实例的equals的结果必须永远相同
  • 如果可组合函数的一个public属性改变了,Composition将会被通知到
  • 所有的public属性类型也都是稳定的

有许多重要的通用类型符合上面的条件,编译器就像对待@Stable注解一样对待这些类型,即使他们没有显式声明@Stable。

  • 所有的原始类型 :Boolean ,Int ,Long,Float,Char 等
  • String
  • 所有的函数类型(lambadas)
    这些类型都符合@Stable,因为他们是不可变的,因为不可变类型是永远不可变的。
    一个值得注意的类型尽管是stable的,但是是可变的,就是Compose的MutableState类型。如果一个值在MutableState中,状态对象总体被认为是稳定的,因为如果State的.value属性发生任何更改,都会通知Compose。
    当作为参数传递给可组合对象的所有类型都稳定时,将根据UI树中可组合位置对参数值进行相等性比较。 如果自上次调用以来所有值均未更改,则跳过重新组合。

关键点:
如果所有输入稳定且未更改,则Compose跳过可组合对象的重组。 比较使用equals方法。

Compose仅在可以证明类型的情况下才认为它是稳定的。 例如,接口通常被视为不稳定,而具有可变的公共属性(其实现可能是不可变的)的类型也不是稳定的。
如果Compose无法推断出某个类型是稳定的,但您想强制Compose将其视为稳定,请使用@Stable注解对其进行标记。

// Marking the type as stable to favor skipping and smart recompositions.
@Stable
interface UiState<T : Result<T>> {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}

在上面的代码片段中,由于UiState是接口,因此Compose通常可以认为此类型不稳定。 通过添加@Stable批注,您可以告诉Compose这种类型是稳定的,从而允许Compose支持智能重组。 这也意味着,如果将接口用作参数类型,则Compose会将其所有实现视为稳定。

状态和effect使用例子

可组合函数应该是无副作用的,当你需要改变APP的状态,您应该使用Effect API,以便以可预测的方式执行那些副作用。

关键字:一个effect,就是一个可组合函数,这个可组合函数不生成UI,而是在组合完成时产生副作用。

由于在Compose中打开效果的可能性不同,因此很容易过度使用它们,确保您在其中进行的工作与UI相关并且不会中断单向数据流

注意:响应式UI本质上是异步的,Jetpack Compose通过在API级别上包含协程而不是使用回调来解决此问题。

LaunchedEffect: 在某个可组合项的作用域内运行挂起函数

为了可以在可重组函数中安全得调用挂起函数,需要用LaunchedEffect可重组函数。当LaunchedEffect进入组合(Composition)时,它将启动协程,并将代码块作为参数传递。 如果LaunchedEffect离开组合,协程将被取消。如果使用不同的键重组LaunchedEffect(请参见下面的Restarting Effects部分),则现有的协程将被取消,新的挂起函数将在新的协程中启动。
例如,在S中显示Snackbar是通过SnackbarHostState.showSnackbar函数完成的,该函数是一个挂起函数

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容