Compose系列 四 生命周期
本系列是我学习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 composition
,Jetpack
会跟踪你所调用的所有可组合函数。然后,当app
的状态改变时,Jetpack
会触发一次recomposition
(翻译成重绘)。重绘是指随着状态的改变,那些把该状态作为入参的可组合函数,会被Jetpack
重新运行一遍。任何状态的改变都是通过重绘的方式反映到UI
上的。
Composition
只能通过initial composition
和recomposition
来生成。而改变一个已经存在的composition
只能通过recomposition
如图,一个可组合函数的生命周期分三部分,分别是initial composition
时进入一个composition
,然后是随着状态的改变recomposition
0次或多次,最后是离开这个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
,但只有第二次才会调用LoginError
。LoginInput
和LoginError
有各自的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
发生了状态改变,所有它们当然是稳定的
有一种特殊类型是Compose
的 MutableState
类型,虽然是可变的,但也被视为稳定类型。如果通过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系列 四 生命周期相关推荐
- Vue 进阶系列丨生命周期
Vue 进阶系列教程将在本号持续发布,一起查漏补缺学个痛快!若您有遇到其它相关问题,非常欢迎在评论中留言讨论,达到帮助更多人的目的.若感本文对您有所帮助请点个赞吧! 2013年7月28日,尤雨溪第一次 ...
- Java高并发编程详解系列-线程生命周期观察者
引言 在之前的博客中我们知道,Thread提供了很多可获取的状态,以及判断是否alive的方法,但是这些方法都是线程本身提供的,在Runnable运行的过程中所处的状态是无法直接获取到的到,例如什 ...
- Android 组件系列-----Activity生命周期
本篇随笔将会深入学习Activity,包括如何定义多个Activity,并设置为默认的Activity.如何从一个Activity跳转到另一个Activity,还有就是详细分析Activity的生命周 ...
- maven系列:生命周期
前面我们详细讲解了maven的一大亮点:依赖,maven做为工程大器,还有个特别重要的功能:构建今天我们主要讲解maven的生命周期,maven的生命周期就是对软件项目构建工作的抽象,一个完整的项目构 ...
- Spring系列之生命周期回调
生命周期回调方法 Spring在容器初始化bean之后(完成依赖注入后)和销毁前都提供了回调的方法,我们称之为生命周期的回调方法.Spring中提供了三种方式来完成生命周期的回调. 官网解释 直接看S ...
- linux 内核维护,Linux 4.18内核系列生命周期结束:用户需尽快更新内核
IT之家11月28日消息 著名的Linux内核维护者Greg Kroah-Hartman宣布Linux 4.18内核系列的生命周期结束,敦促用户尽快将他们的发行版升级到更新的内核. Linux 4.1 ...
- 深度挖掘 Laravel 生命周期
本文首发于个人博客 深度挖掘 Laravel 生命周期,转载请注明出处. 这篇文章我们来聊聊 「Laravel 生命周期」 这个主题.虽然网络上已经有很多关于这个主题的探讨,但这个主题依然值得我们去研 ...
- Vue缓存路由(keep-alive)以及新的生命周期
一.概念 也就是说,kee-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染 .也就是所谓的组件缓存 keep-alive 是 Vue 的内置组件,当它包裹动态组 ...
- React组件(进阶--生命周期 )
目录 一.生命周期 - 概述 二.生命周期 - 挂载阶段 三.生命周期 - 更新阶段 四.生命周期 - 卸载阶段 文章推荐: 1.JSX基础(入门) 2.React组件(入门) 3.React组件动状 ...
最新文章
- linux跨主机复制文件
- 安装mysql5.15.7版本_YUM方法安装mysql5.7版本
- android访问html页面
- SAP Spartacus home页面的layout,template,section和slots
- 全志A33-串口的使用
- C++学习笔记25,析构函数总是会宣布virtual
- 第五十五期:区块链将在2020年实现的重大改变
- 从P560小型机B181201B故障代码识别手把手详解
- Random()中具体实现(含种子数组的实现)
- 史上最强DIY,手工制作一只会说话的机器狗
- Linux开机启动过程(4):切换到64位模式-长模式(直到内核解压缩之前)
- excel填充序列_零基础、初学者必须掌握的10个Excel技巧,办公必备!
- three.js使用OrbitControls.js控制几何体旋转、平移、缩放
- 彭亚雄:7月24日阿里云上海峰会企业存储大神
- 解决Java提示“编码GBK的不可映射字符”的问题
- week15(字符串集合:Hash、字典树、KMP)
- 江苏省2021年高考成绩查询入口,江苏省教育考试院2021年江苏高考成绩查询时间及系统入口【预计6月24日起查分】...
- 什么是同源策略,为什么浏览器要使用同源策略
- 数据库原理与应用实验九 视图的使用
- 你我许的誓言也许抵不过岁月的云烟