聊聊 Jetpack Compose 的 “状态订阅自动刷新” -- 你真的了解重组吗?

Jekpack Compose “状态订阅&自动刷新” 系列:

     【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - MutableState/mutableStateOf 】

     【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - remember 和重组作用域 】

     【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - 有状态、无状态、状态提升?】

     【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - mutableStateListOf 】

     【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - 你真的了解重组吗?】


先考虑一个问题,什么时候会重组?

val name by remember { mutableStateOf("Hi Compose")}
val nums = mutableStateListOf(1, 2, 3)
val maps = mutableStateMapOf(1 to "One", 2 to "Two")

通过之前的几篇文章,你应该对这三行代码不陌生,当用 mutableState*Of 申明一个变量的时候,在可组合项中,如果变量变化了,就会触发重组。

比如下面代码:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var name by mutableStateOf("Hi, Compose")setContent {Column {Text(name, Modifier.clickable { name = "Hi, Kotlin" })}}}
}

运行:

在这里插入图片描述

Text(name) 被重组了,但是并不是仅仅 Text(name) 被重组,而是整个 Lambda 表达式,我们之前说过,这就是重组作用域。

在这里插入图片描述

那么思考一个问题:

在这里插入图片描述

我们先看一个好玩的东西,查看 Column 函数:

@Composable
inline fun Column(modifier: Modifier = Modifier,verticalArrangement: Arrangement.Vertical = Arrangement.Top,horizontalAlignment: Alignment.Horizontal = Alignment.Start,content: @Composable ColumnScope.() -> Unit
) {val measurePolicy = columnMeasurePolicy(verticalArrangement, horizontalAlignment)Layout(content = { ColumnScopeInstance.content() },measurePolicy = measurePolicy,modifier = modifier)
}

Column 是个 inline 函数(内联函数),这就意味着在实际编译之前,这个函数的调用会被替换成内部实际的代码,比如会是下面这个样子:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var name by mutableStateOf("Hi, Compose")setContent {// Column {// 替换为Layout(Text(name, Modifier.clickable { name = "Hi, Kotlin" }))// }}}
}

再来看下 Layout():

@Composable inline fun Layout(content: @Composable @UiComposable () -> Unit,modifier: Modifier = Modifier,measurePolicy: MeasurePolicy
) {... ...ReusableComposeNode<ComposeUiNode, Applier<Any>>(... ...content = content)
}

也是个 inline 函数,那么代码又回变成这样:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var name by mutableStateOf("Hi, Compose")setContent {// Column {// Layout(// 替换为ReusableComposeNode(Text(name, Modifier.clickable { name = "Hi, Kotlin" }))// )// }}}
}

再来看下 ReusableComposeNode:

@Composable @ExplicitGroupsComposable
inline fun <T, reified E : Applier<*>> ReusableComposeNode(noinline factory: () -> T,update: @DisallowComposableCalls Updater<T>.() -> Unit,noinline skippableUpdate: @Composable SkippableUpdater<T>.() -> Unit,content: @Composable () -> Unit
) {... ...content()... ...
}

又是 inline 函数,而且你看它内部干啥了?直接调用了 content()

所以最终代码在编译前其实会把 Column {} 给拿掉,就跟下面一样:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var name by mutableStateOf("Hi, Compose")setContent {// Column {// Layout(// 替换为// ReusableComposeNode(Text(name, Modifier.clickable { name = "Hi, Kotlin" })// )// )// }}}
}

那么这就意味着:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var name by mutableStateOf("Hi, Compose")setContent {// 组件 1,这个地方会因为 name 的改变而一起重新调用Column {// 组件 2,这个地方会因为 name 的改变而一起重新调用Text(name, Modifier.clickable { name = "Hi, Kotlin" })// 组件 3,这个地方会因为 name 的改变而一起重新调用}// 组件4,这个地方会因为 name 的改变而一起重新调用}}
}

为了验证,我们测试下:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var name by mutableStateOf("Hi, Compose")setContent {println("Recompose 范围测试 1")Column {println("Recompose 范围测试 2")Text(name, Modifier.clickable { name = "Hi, Kotlin" })println("Recompose 范围测试 3")}println("Recompose 范围测试 4")}}
}

运行:

在这里插入图片描述

这个 Log 没有任何问题,现在我们点击:

在这里插入图片描述

Log 已经验证了我们刚才的结论,那这就涉及到了一个问题:性能风险!

比如下面的代码有可能出现在你的代码中:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var name by mutableStateOf("Hi, Compose")setContent {println("Recompose 范围测试 1")Column {println("Recompose 范围测试 2")Text(name, Modifier.clickable { name = "Hi, Kotlin" })println("Recompose 范围测试 3")}NBFunction()println("Recompose 范围测试 4")}}
}@Composable
fun NBFunction() {println("Recompose 范围测试:我干了很多消耗性能的事~~~")// 这里面干了很多 NB 的事,逻辑量特别大
}

你写了一个很牛逼的 Composable 函数,里面干了很多复杂的事,特别消耗性能,那么每次随着 name 的更改,NBFunction() 都会被执行一遍,就就会存在巨大的性能消耗。

你要不信,我们运行一下,看下 Log:

在这里插入图片描述

🤔???点击 name 后,NBFunction() 竟然没有再次执行?但其他 Log 仍然执行了啊!

难道 NBFunction() 没有被调用?

其实 NBFunction() 被调用了,只不过进入 NBFunction() 内部后,内部的代码没有被执行。

🤔???都进来了,你跟我说内部代码不执行?-- 对!

其实这是因为:在 Compose 的编译过程中,编译器插件会做干预,这个干预过程会修改我们的 Compose 函数,比如说它会给函数内部的代码加上一些条件判断,判断这个函数的参数跟上一次函数被调用的时候传入的参数有没有改变,如果没有改变,就直接跳过这个函数的内部代码的执行,这是 Compose 的优化。

而你看 NBFunction() 这个函数有参数吗?-- 没有,所以它内部代码永远不会执行,所以 Log 肯定不会打印出来。

来试一下,改下代码:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var name by mutableStateOf("Hi, Compose")setContent {println("Recompose 范围测试 1")Column {println("Recompose 范围测试 2")Text(name, Modifier.clickable { name = "Hi, Kotlin" })println("Recompose 范围测试 3")NBFunction(name)}println("Recompose 范围测试 4")}}
}@Composable
fun NBFunction(name: String) {println("Recompose 范围测试:我干了很多消耗性能的事~~~")// 这里面干了很多 NB 的事,逻辑量特别大Text("$name")
}

我们给 NBFunction 加个参数,那么随着 name 的改变,看看它会不会被执行:

在这里插入图片描述

结果显而易见了!Compose 重组过程中会判断 NBFunction() 的参数 String 是否变化,那么如果我的参数是一个对象呢?

比如我传入的是:

data class User(val name: String)

Compose 在重组过程中,依然会对对象类型的参数做判断,不过它的判断规则是 Kotlin 中的 “==”,等同于 Java 的 “equals”,是结构性相等判断。

现在我们修改下代码:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var name by mutableStateOf("Hi, Compose")var user = User("marco")  // User 对象setContent {println("Recompose 范围测试 1")Column {println("Recompose 范围测试 2")Text(name, Modifier.clickable {name = "Hi, Kotlin"user = User("marco")  // 点击后,新的 User 对象})println("Recompose 范围测试 3")NBFunction(user)}println("Recompose 范围测试 4")}}
}@Composable
fun NBFunction(user: User) {println("Recompose 范围测试:我干了很多消耗性能的事~~~")// 这里面干了很多 NB 的事,逻辑量特别大Text("${user.name}")
}data class User(val name: String)

代码很简单,我们通过 Log 验证下:

在这里插入图片描述

我们再改下代码,把 User 换成一个新的内容:

var user = User("marco")Text(name, Modifier.clickable {name = "Hi, Kotlin"user = User("marco_2")
})

再看下 Log:

在这里插入图片描述

确实如我们前面预想一样,Compose 重组过程中同样会对 NBFunction() 的对象参数也做判断,是结构性相等判断。

接下来我们看个有意思的东西,我们再来改一下代码:

var user = User("marco")Text(name, Modifier.clickable {name = "Hi, Kotlin"user = User("marco"). // user 改回来,都是 marco 内容
})==> 此时肯定是会判断相等,不会执行 NBFunction() 内部的代码
---
==> 接下来我们只改一下关键字:
data class User(val name: String)
// val --> var
data class User(var name: String)

仅仅把 val 改成 var,运行看 Log:

在这里插入图片描述

🤔???val 就会跳过 NBFunction() 内部代码,var 就不会跳过 NBFunction() 内部代码?Why?

这是因为 Compose 的本身机制设定:

data class User(val name: String)  // Compose 会认为这是一个可靠的类
data class User(var name: String)  // Compose 会认为这是一个不可靠的类

对于不可靠的类,我就不管你了,直接进!这是因为出于界面正确性的考虑。

比如我们修改下代码:

在这里插入图片描述

所以,与性能相比,准确性才是最终要的,所以就会无条件的进入 NBFunction() 函数再执行一遍。

那如果假设我们可以保证不会出现以上情况,保证 User 对象永远相等,希望 Composable 插件也可以跳过内部执行,提升性能,如何做呢?

用 @Stable 注解,它是一个稳定性注解,告诉 Compose 编译器插件,这个类是可靠的。这样 Compose 重组过程中就会跳过 NBFunction() 内部代码。

@Stable
data class User(var name: String)

运行看下 Log:

在这里插入图片描述

除了 @Stable 可以认定可靠以外,还有一种方式可以告诉 Compose 是可靠的:

class User(name: String) {var name by mutableStateOf(name)  // 这是一种更通用的写法
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/198483.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Cloud 配置 Druid(二)

不废话&#xff0c;直接上代码&#xff0c; Nacos搭建的微服务&#xff0c;可以看Spring Cloud 配置 Nacos&#xff08;一&#xff09;-CSDN博客 一&#xff0c;pom文件 spring-cloud-starter-alibaba-nacos-discovery 和 spring-cloud-starter-openfeign 都是基于spring-cl…

Apollo新版本Beta技术沙龙的参会感受

Apollo新版本Beta技术沙龙的参会感受 Apollo新版本Beta技术沙龙的参会感受摘要 &#x1f697;&#x1f310;参会流程 &#x1f5d3;️展厅参观/展厅讲解 &#x1f3e2;进入百度Apollo未来驾驶汽车5G云代驾的神奇签到 &#x1f4dd;Apollo新版本Beta整体介绍 &#x1f680;技术分…

C语言:用递归的方法求斐波那契数列:1,1,2,3,5,8,……的前40个数

分析&#xff1a; 首先&#xff0c;在代码的起始部分&#xff0c;包含<stdio.h>头文件&#xff0c;这个头文件提供了输入和输出的函数。 然后&#xff0c;定义了四个变量&#xff1a;f、f1、f2和i。f1和f2是斐波那契数列的前两个数字&#xff0c;初始化为1。f是当前计…

qt使用wimlib-imagex,做windows系统备份还原

wimlib-imagex是个第三方工具&#xff0c;可对系统映像进行操作&#xff0c;下载地址&#xff1a; https://wimlib.net/downloads/index.html 程序主要用到以下这两个文件&#xff1a;libwim-15.dll和wimlib-imagex.exe wimlib-imagex.exe的调用命令参数&#xff0c;可以通过…

【Docker】资源配额及私有镜像仓库

资源配额及私有镜像仓库 一、Docker资源配额1.1、控制cpu1.1.1、cpu份额控制1.1.2、core核心控制1.1.3、配额控制参数的混合使用 1.2、控制内存1.3、控制IO1.4、资源释放 二、Docker私有镜像仓库Harbor2.1、Harbor简介2.2、为Harbor自签发证书【1】生成ca证书【2】生成域名的证…

输出完全二叉树中某个结点的双亲和所有子孙。假设完全二叉树的顺序存储在一维数组A[n]中。

思路&#xff1a; 首先定义两个函数&#xff0c;getParent函数用于获取指定结点的双亲结点的索引&#xff0c;printDescendants函数用于输出指定结点的所有子孙。然后在main函数中&#xff0c;创建表示完全二叉树的数组A&#xff0c;并针对指定结点索引进行相关操作&#xf…

HOST文件被挟持,无法上网,如何解决。

问题&#xff1a; 晚上开机&#xff0c;突然发现无法联网&#xff0c;提示网络异常 解决&#xff1a; 首先网络诊断&#xff0c;host文件被劫持&#xff0c;修复后&#xff0c;仍然不行。 然后测试手机热点&#xff0c;发现仍然无法联网 尝试用火绒修复&#xff0c;无果。 所有…

Python 解析JSON实现主机管理

JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;它以易于阅读和编写的文本形式表示数据。JSON 是一种独立于编程语言的数据格式&#xff0c;因此在不同的编程语言中都有对应的解析器和生成器。JSON 格式的设计目标是易于理解、…

【解决方案】环保设备用电与电网数据集中展示平台的应用与研究

摘 要&#xff1a;近年来&#xff0c;信息化不断推进&#xff0c;政府工作方式以及职能不断改革&#xff0c;许多省级环保部门开展环保与信息技术的融合&#xff0c;用于推进环保的发展。本文基于环境监测研究省级环保与电网数据集中展示平台的应用。环境监测是环保工作的重要组…

读书笔记-《数据结构与算法》-摘要1[数据结构]

文章目录 [数据结构]1. String - 字符串2. Linked List - 链表2.1 链表的基本操作2.1.1 反转链表单向链表双向链表 2.1.2 删除链表中的某个节点2.1.3 链表指针的鲁棒性2.1.4 快慢指针 3. Binary Tree - 二叉树3.1 树的遍历3.2 Binary Search Tree - 二叉查找树 4. Queue - 队列…

JSP入门+EL表达式+JSTL标签

1.JSP&#xff1a; 1.指令 2.注释 3.内置对象 2.MVC开发模式 3.EL表达式 4.JSTL标签 5.三层架构 ## JSP&#xff1a; 1.指令 *用于配置JSP页面&#xff0c;导入资源文件 *格式&#xff1a;<% 指令名称 属性名1属性值1 属性名2属性值2 .......%> *分类&#xff1…

格雷希尔帮助仪器仪表测试时快速密封的G60C系列接头其优势有哪些

仪器仪表在工业领域中扮演着重要的角色&#xff0c;如&#xff1a;压力表&#xff0c;压力传感器、压力变送器、压力开关、压力歧管等这些&#xff0c;在工业领域中都是随处可见的&#xff0c;其数据的精度直接影响着产品在生产过程中的质量和安全性&#xff1b;因此&#xff0…

食品行业研究:金枪鱼产业发展及市场消费分析

金枪鱼是无污染、高档、美味、安全、健康的绿色海洋动物食品&#xff0c;是国际营养协会推荐的世界三大营养鱼种之一 ,它凭借较高的经济价值、较广的分布范围、丰富的资源储量等优势&#xff0c;成为当今世界远洋渔业发展的关注重点和国际水产品贸易的主要鱼种。 金枪鱼类是高度…

3分钟,全方面了解透明oled拼接屏

透明OLED拼接屏是一种先进的显示技术&#xff0c;它具有透明度高、色彩鲜艳、轻薄柔韧、拼接灵活、功耗低、寿命长等特点。在商业、教育、展示、娱乐等领域&#xff0c;透明OLED拼接屏的应用越来越广泛。 在商业领域&#xff0c;透明OLED拼接屏可以作为商品展示柜&#xff0c;通…

系统运维工具KSysAK——让运维回归简单

系统运维工具KSysAK——让运维回归简单 1.基本信息 1.1概述 系统异常定位分析工具KSysAK是云峦操作系统研发及运维人员总结开发及运维经验&#xff0c;设计和研发的多个运维工具的集合&#xff0c;可以覆盖系统的日常监控、线上问题诊断和系统故障修复等常见运维场景。 工具…

从一个bug认识 Spring 单例模式

大家好&#xff0c;我是风筝&#xff0c;公众号「古时的风筝」 谁还没在 Spring 里栽过跟头呢&#xff0c;从哪儿跌倒&#xff0c;就从哪儿睡一会儿&#xff0c;然后再爬起来。 讲点儿武德 这是由一个真实的 bug 引起的&#xff0c;bug 产生的原因就是忽略了 Spring Bean 的…

网络层之无分类编址CIDR(内涵计算例题)

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

初学vue3与ts:element-plus的警告(Extraneous non-props attributes (ref_key) ...)

用了vue3与ts&#xff0c;ui我就选了element-plus element-plus官网&#xff1a;https://element-plus.org/zh-CN/ element-plus官网(国内镜像站点)&#xff1a;https://element-plus.gitee.io/zh-CN/ 国内镜像站点如果进不去的话&#xff0c;在element-plus官网最下面的链接-&…

Jupyter Notebook中设置Cell主题

1. 获取本机Jupyter的配置目录 C:\Users\Administrator>jupyter --data-dir C:\Users\Administrator\AppData\Roaming\jupyter2. 进入获取的目录&#xff0c;创建指定路径 C:\Users\Administrator>cd C:\Users\Administrator\AppData\Roaming\jupyter C:\Users\Adminis…

TikTok新闻视角:短视频如何改变信息传递方式?

随着数字时代的不断发展&#xff0c;信息传递的方式也在不断演变。近年来&#xff0c;短视频平台TikTok崭露头角&#xff0c;通过其独特的15秒短视频形式&#xff0c;逐渐在新闻传播领域占据一席之地。本文将深入探讨TikTok在新闻视角下是如何改变信息传递方式的&#xff0c;以…