本系列是我学习compose过程中,对官方文档的翻译和解读,以及实验性的Demo工程。主要参考官方文档和中文手册

全部的正文内容(Demo工程除外)源自Compose官方文档,个人解读以引用的形式插入。

Compose 官方文档 https://developer.android.google.cn/jetpack/compose

Compose 中文手册 https://compose.net.cn/

本文翻译内容 https://developer.android.google.cn/jetpack/compose/lifecycle

生命周期的概述

Composition(翻译成绘制)是运行可组合函数后生成的结果,它是一个树状的结构,描述着app的各个UI

Jetpack第一次运行可组合函数时,也叫作initial compositionJetpack会跟踪你所调用的所有可组合函数。然后,当app的状态改变时,Jetpack会触发一次recomposition(翻译成重绘)。重绘是指随着状态的改变,那些把该状态作为入参的可组合函数,会被Jetpack重新运行一遍。任何状态的改变都是通过重绘的方式反映到UI上的。

Composition只能通过initial compositionrecomposition来生成。而改变一个已经存在的composition只能通过recomposition

如图,一个可组合函数的生命周期分三部分,分别是initial composition时进入一个composition,然后是随着状态的改变recomposition0次或多次,最后是离开这个composition

重绘往往是通过State对象的变化触发的。Jetpack Compose会跟踪这些State对象,当它们改变时,把它们作为入参的可组合函数们也被重新执行。

如果一个可组合函数被重复调用了多次,那么在该Composition中会创建多个实例,每一个实例有独立的生命周期

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

Compose中每一个UI组件的生命周期被大大简化了,而不是像传统View中的一系列过程

所以我们编码时,只需要关注三个点。一是UI的初始化时机,二是UI的重绘时机,三是UI的Remove时机

对Composition中一个Composable的解析

Composition对每一个Composable的实例分配一个独一无二的标识,这个标识由该Composable的调用点决定,称为call site。在不同的call site调用同一个可组合函数,会创建多个不同的Composable实例

call site就是源代码中可组合函数的调用位置

如果一个可组合函数发生了重绘,且重绘前后的两次绘制中,调用了不同的子可组合函数,Compose会根据call site标记出哪些是两次都调用了的,哪些是只有第二次才调用的。对于前者,如果它们的入参没有改变,那就跳过。对于后者,则是initial composition。如下例

//假设第一次showError是False
//第二次showError是True
@Composable
fun LoginScreen(showError: Boolean) {if (showError) {LoginError()}LoginInput()
}@Composable
fun LoginInput() { /* ... */ }

LoginScreen发生重绘前后,都会调用LoginInput,但只有第二次才会调用LoginErrorLoginInputLoginError有各自的call site作为在composition中的标识

图中LoginInput颜色相同表示没有重绘。尽管第一次绘制时,LoginInput的调用顺序是首位,第二次绘制时,变成了第二位,但在Composition眼里,它的call site始终没变。同时,LoginInput的入参也没变,所以它就被跳过了重绘

通过附加信息实现更智能的重绘

多次调用同一个可组合函数会向Composition中添加多个实例。但如果多次调用时的call site都相同,Compose就无法通过call site来区分这些实例。因此Compose会默认引入执行顺序作为附加信息,来区分这些实例。这种默认行为有些时候符合预期,但有些时候不符合预期。

@Composable
fun MoviesScreen(movies: List<Movie>) {Column {for (movie in movies) {//这里的MovieOverview的call site是固定的//区分多个实例的方法是for循环的执行顺序MovieOverview(movie)}}
}

上例中Compose使用执行顺序作为附加信息来区分多个实例。如果一个新的movie被添加到movies的尾部,那么重绘时,Compose可以复用之前的实例,因为它们的执行顺序没有改变。

但是,如果 movies 在其他位置插入或删除了movie,这会导致所有入参改变的MovieOverview发生重绘。这看起来问题不大,不过是多重绘了几个组件而已,但事实上某些情况下会导致严重的性能问题。

例如,MovieOverview需要使用side effect去请求一张图片。那么如果重绘发生时,effect正在进行,那么这个effect就会被重启。

关于side effect请见下一篇文章。这里姑且理解为开了一个后台线程,去请求网络资源,如果重绘发生时还没有请求成功,那么就会重启这个请求。众所周知,建立一个网络连接是耗时的,我们肯定不希望反复建立网络连接

@Composable
fun MovieOverview(movie: Movie) {Column {val image = loadNetworkImage(movie.url)MovieHeader(image)}
}

本次重绘时MovieOverview 无法被复用,不同的颜色表示它们发生了重绘

理论上,我们希望把MovieOverview的实例和传入的movie绑定起来,也就是说当插入一个新的movie时,我们希望仅仅是在Composition的树状结构中调整这些实例的位置,而不是重绘这些实例。Compose提供了一个key composable的函数,能允许我们实现这一想法

通过用key函数包裹住代码块,同时传入一个或多个参数作为标识,这些参数就会与代码块下的实例绑定起来。传入的参数没必要是全局下都不同的,只需要在该call site的局部作用域下是各不相同的就行。例如,每一个movie需要一个独一无二的id作为key的入参,只需要保证在for循环下的局部作用域中,这些id各不相同即可。

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

使用上述代码,当movies列表改变时,Compose能复用那些已经绘制了的实例

有一些UI组件已经内置了这种功能,比如LazyColumn可以直接传入一个lambda表达式作为key参数

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

这一部分的核心思想是,可组合函数与实际的UI实例之间,是一对多的关系

Compose优先通过Call Site来区分这些实例,如果Call Site相同,就用执行顺序来区分

如果你希望UI实例能和数据一一对应起来,使用Key方法,能给UI实例绑定自定义的标识符

入参不变跳过重绘

这个概念提了很多遍了,但什么叫入参不变呢?Compose规定入参不变是指,入参的类型是稳定的,且值没有发生改变。

所谓类型是稳定的,需要满足下面三个准则:

  • 对于两个相同的该类型的实例,equals的结果永远相等
  • 该类型实例的公共属性发生改变时,Compose能接收到通知
  • 该类型的所有公共属性的类型也是稳定的

Compose默认的稳定类型是State类型,如果你希望把自定义类型的实例传入可组合函数作为稳定的入参,那么需要声明@State注解。但有一些常用的类型,虽然没有声明@State注解,也被Compose视为稳定的类型

  • 所有基本类型 Boolean, Int, Long, Float, Char, etc.
  • Strings
  • All Function types (lambdas)

这些类型之所以是稳定的,是因为它们都是不可变的。不可变的类型实例就不需要通知Compose发生了状态改变,所有它们当然是稳定的

有一种特殊类型是ComposeMutableState 类型,虽然是可变的,但也被视为稳定类型。如果通过MutableState保存一个值,那么这个对象被视为稳定的,因为这个值的任何改变都会通知给Compose

如果一个可组合函数的所有入参都是稳定类型的,那么Compose在重绘时就会根据UI树中保存的数据进行值的比较,如果重绘前后的所有入参的值都相等,就跳过重绘。这里的比较其实就是调用equals方法

只有当类型能够证明它是稳定的时候,Compose才会认为它是稳定的。例如,接口通常被视为不稳定的,具有可变公共属性(即便其实例被声明为val不可变)的类型也被视为不稳定

如果Compose不能推断出一个类型是稳定的,但你想强迫它认同,可以使用注解 @Stable

@Stable
interface UiState<T : Result<T>> {val value: T?val exception: Throwable?val hasError: Booleanget() = exception != null
}

这个例子中,因为UiState 是个接口类型,Compose默认认为所有接口类型都是不稳定的。但通过添加注解,你可以强行告诉Compose它是稳定的,之后Compose就会把UIState类型以及它的所有实现都视为稳定的。这种方法能实现自定义的智能重绘

Compose系列 四 生命周期相关推荐

  1. Vue 进阶系列丨生命周期

    Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的.若感本文对您有所帮助请点个赞吧! 2013年7月28日,尤雨溪第一次 ...

  2. Java高并发编程详解系列-线程生命周期观察者

    引言   在之前的博客中我们知道,Thread提供了很多可获取的状态,以及判断是否alive的方法,但是这些方法都是线程本身提供的,在Runnable运行的过程中所处的状态是无法直接获取到的到,例如什 ...

  3. Android 组件系列-----Activity生命周期

    本篇随笔将会深入学习Activity,包括如何定义多个Activity,并设置为默认的Activity.如何从一个Activity跳转到另一个Activity,还有就是详细分析Activity的生命周 ...

  4. maven系列:生命周期

    前面我们详细讲解了maven的一大亮点:依赖,maven做为工程大器,还有个特别重要的功能:构建今天我们主要讲解maven的生命周期,maven的生命周期就是对软件项目构建工作的抽象,一个完整的项目构 ...

  5. Spring系列之生命周期回调

    生命周期回调方法 Spring在容器初始化bean之后(完成依赖注入后)和销毁前都提供了回调的方法,我们称之为生命周期的回调方法.Spring中提供了三种方式来完成生命周期的回调. 官网解释 直接看S ...

  6. linux 内核维护,Linux 4.18内核系列生命周期结束:用户需尽快更新内核

    IT之家11月28日消息 著名的Linux内核维护者Greg Kroah-Hartman宣布Linux 4.18内核系列的生命周期结束,敦促用户尽快将他们的发行版升级到更新的内核. Linux 4.1 ...

  7. 深度挖掘 Laravel 生命周期

    本文首发于个人博客 深度挖掘 Laravel 生命周期,转载请注明出处. 这篇文章我们来聊聊 「Laravel 生命周期」 这个主题.虽然网络上已经有很多关于这个主题的探讨,但这个主题依然值得我们去研 ...

  8. Vue缓存路由(keep-alive)以及新的生命周期

    ​ 一.概念 也就是说,kee-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染 .也就是所谓的组件缓存 keep-alive 是 Vue 的内置组件,当它包裹动态组 ...

  9. React组件(进阶--生命周期 )

    目录 一.生命周期 - 概述 二.生命周期 - 挂载阶段 三.生命周期 - 更新阶段 四.生命周期 - 卸载阶段 文章推荐: 1.JSX基础(入门) 2.React组件(入门) 3.React组件动状 ...

最新文章

  1. linux跨主机复制文件
  2. 安装mysql5.15.7版本_YUM方法安装mysql5.7版本
  3. android访问html页面
  4. SAP Spartacus home页面的layout,template,section和slots
  5. 全志A33-串口的使用
  6. C++学习笔记25,析构函数总是会宣布virtual
  7. 第五十五期:区块链将在2020年实现的重大改变
  8. 从P560小型机B181201B故障代码识别手把手详解
  9. Random()中具体实现(含种子数组的实现)
  10. 史上最强DIY,手工制作一只会说话的机器狗
  11. Linux开机启动过程(4):切换到64位模式-长模式(直到内核解压缩之前)
  12. excel填充序列_零基础、初学者必须掌握的10个Excel技巧,办公必备!
  13. three.js使用OrbitControls.js控制几何体旋转、平移、缩放
  14. 彭亚雄:7月24日阿里云上海峰会企业存储大神
  15. 解决Java提示“编码GBK的不可映射字符”的问题
  16. week15(字符串集合:Hash、字典树、KMP)
  17. 江苏省2021年高考成绩查询入口,江苏省教育考试院2021年江苏高考成绩查询时间及系统入口【预计6月24日起查分】...
  18. 什么是同源策略,为什么浏览器要使用同源策略
  19. 数据库原理与应用实验九 视图的使用
  20. 你我许的誓言也许抵不过岁月的云烟

热门文章

  1. 226. 翻转二叉树【58】
  2. ofo使用感言--起风了
  3. WPS-脚注文本不对,脚注不分栏,脚注调节,特殊符号脚注。
  4. 个性签名【2021】
  5. 2019天梯赛 吃鱼还是吃肉 (10 分)
  6. ryzen7 1700蓝屏问题
  7. 浏览器是什么? (类似socket客户端)
  8. 运行html后电脑不能上网了,路由器安装后电脑无法上网解决办法
  9. i5 10600kf配什么主板 i5 10600kf配什么显卡
  10. 一个程序员的日常!是我本人了,没错了