Jetpack Compose是如何决定哪块代码进行重组的?

news2024/11/26 3:11:03

作者:bytebeats

Jetpack Compose重组的作用域

几个月前, 我开始在生产级应用中使用Jetpack Compose, 当然是在编写了一些"Jetpack Compose Hello World项目"作为示例应用之后, 当然之后我放弃了所有这些项目. 在生产级应用中使用Jetpack Compose相当具有挑战性, 因为我们需要真正理解我们正在编写的内容, 以及它对性能和优化的影响, 并确保它不会重现意想不到的行为/错误, 尽管在底层有很多东西我们可能还不知道Jetpack Compose是如何处理的, 其中之一就是重组.

重组是当输入发生变化时再次调用可组合函数的过程.

函数的输入发生变化时, 就会发生这种情况. 当Compose根据新的输入进行重组时, 它只会调用可能已发生变化的函数或lambdas, 而跳过其他函数或lambdas. 通过跳过所有参数未发生变化的函数或lambdas, Compose可以高效地进行重组.

简单地说, 重组就是重新渲染Compose组件的过程, 因为输入发生了变化, 我们需要向用户显示最新的输入. 如果我们经常在安卓视图上创建自定义视图, 我们可以使用invalidate()函数重新渲染整个自定义视图组件. 重组与invalidate()的功能类似, 都是重新渲染当前视图, 但重组的工作方式更智能, 只重组输入发生变化的组件, 而跳过其他组件, 这使得Jetpack Compose中的重组比Android视图中的invalidate()更有效.

invalidate() vs recomposition

当我们使用Compose构建用户界面时, 重组是不可避免的, 因为重组是Compose将当前数据状态更新到用户界面的机制, 但我们可以消除不必要的重组, 以优化我们的Compose代码. 通过了解重组的工作原理, 我们可以确保自己编写的代码不会导致Compose重复进行不必要的重组.

重组作用域

什么是重组作用域?

重组作用域是可以独立重组的最小代码块. 它意味着什么? 这意味着编译器能够找到状态或输入读取的位置, 并只重新编译组件的最小作用域, 而无需重新编译所有组件. 让我们看看下面的重组作用域示例:

重组作用域

重组作用域通常以开头和结尾的函数括号标记, 在上面的示例中, 有两个重组作用域, 即Greeting作用域Button作用域. 在层次结构中, 我们可以看到Button作用域也在Greeting作用域的内部, 但在重组作用域中, Greeting作用域Button作用域可以独立重组, Compose可以重组Greeting作用域而不重组Button作用域, 也可以重组Button作用域而不重组Greeting作用域, 或者在一个重组过程中同时重组这两个作用域.

为了了解重组作用域是如何工作的, 让我们做一些实验.

第一部分 - 理解重组作用域

在开始之前, 我们需要创建一个函数来跟踪重组, 这个函数将在每次重组发生时调用, 并计算已经调用了多少次重组. 我们需要让这个函数内联, 以确保这个可组合函数没有自己的重组作用域.

class Ref(var value: Int)

@Composable
inline fun LogCompositions(msg: String) {
    val ref = remember { Ref(0) }
    SideEffect { ref.value++ }
    Log.d("RecompositionLog", "Compositions: $msg ${ref.value}")
}

在这一部分中, 我们将了解重组作用域是如何工作的. 让我们看看下面的示例, 当我们在Android Studio上创建撰写活动时, 我们修改了默认的"Greeting"屏幕, 以展示重组作用域是如何工作的.

@Composable
fun Greeting() {
    var state by remember {
        mutableStateOf("Hi Foo")
    }
    LogCompositions(msg = "Greeting Scope")
    Text(text = state)
    Button(
        onClick = { state = "Hi Foo ${Random.nextInt()}" },
        modifier = Modifier
            .padding(top = 32.dp)
    ) {
        LogCompositions(msg = "Button Scope")
        Text(
            text = "Click Me!"
        )
    }
}

在这个示例中, 我们创建了一个文本和一个按钮, 只要点击按钮, 就会更新文本读取的状态. 这里有两个组成作用域:“Greeting作用域"和"Button作用域”. 正如我之前解释过的, 这两个作用域都可以独立执行. 让我们试着运行这个示例.

结果:Greeting作用域

"Greeting作用域"被调用, 但"Button作用域"没有被调用. 为什么? 因为state是在第7行读取的, 而该LoC是 Greeting作用域的一部分. 为什么Button作用域没有被调用? 因为在Button作用域上没有任何组成组件读取可观察的状态.

使状态声明更接近调用者

在上一个示例中, Compose重组了"Greeting作用域", 结果是每次点击按钮时都会调用"Greeting作用域"的 LogComposition. 如果我们移动状态和LogComposition的顺序呢? 会不会因为我们在LogComposition行之后才定义和读取状态而导致LogComposition不被调用? 让我们试试看.

@Composable
fun Greeting() {
    LogCompositions(msg = "Greeting Scope")
    var state by remember {  //We move this line of code after log recomposition and closer to its caller and 
        mutableStateOf("Hi Foo")
    }
    Text(text = state)
    Button(
        onClick = { state = "Hi Foo ${Random.nextInt()}" },
        modifier = Modifier
            .padding(top = 32.dp)
    ) {
        LogCompositions(msg = "Button Scope")
        Text(
            text = "Click Me!"
        )
    }
}

结果:

结果:Greeting作用域–使状态更接近调用者

结果保持不变, 因为Compose不是按代码行调用的, 而是按组成作用域调用的, 其中LogRecomposition和Text在同一个组成作用域中读取状态, 移动顺序不会产生任何影响.

将读取状态的组件移到另一个作用域

现在让我们把读取状态的组件移到另一个作用域, 从Greeting作用域移到Button作用域如下所示:

@Composable
fun Greeting() {
    var state by remember {
        mutableStateOf("Hi Foo")
    }
    LogCompositions(msg = "Greeting Scope")
    Text(text = "Hi Foo")
    Button(
        onClick = { state = "Hi Foo ${Random.nextInt()}" },
        modifier = Modifier
            .padding(top = 32.dp)
    ) {
        LogCompositions(msg = "Button Scope")
        Text(
            text = state
        )
    }
}

结果:

结果 : Greeting作用域 - 将状态移动到另一个组成作用域

是的, 正如预期的那样:Button作用域被调用, 因为我们将读取状态的组件移动到了Button作用域. 尽管根据层次结构, Button作用域 也是Greeting作用域 的一部分, 但Compose能够调用它而不调用其父作用域组件.

第二部分 - 内联可组合函数

我们在此添加新组件, 即Column. 我相信大多数人在Jetpack Compose中创建用户界面时都会用到这个组件. 在我们尝试运行这个示例之前, 根据第一部分的示例, 这段代码将只调用Column作用域, 对吗? 因为在第9行读取状态的组件位于Column作用域. 让我们来看看.

@Composable
fun Greeting() {
    var state by remember {
        mutableStateOf("Hi Foo")
    }
    LogCompositions(msg = "Greeting Scope")
    Column { // We add new component here
        LogCompositions(msg = "Column Scope")
        Text(text = state)
        Button(
            onClick = { state = "Hi Foo ${Random.nextInt()}" },
        ) {
            LogCompositions(msg = "Button Scope")
            Text(
                text = "Click Me"
            )
        }
    }
}

结果:

结果:内联可组合函数

好吧, 这是不应该发生的. Column作用域被调用了, 但为什么Greeting作用域也被调用了呢? 要回答这个问题, 让我们打开Column的定义

内联Column函数

Column(以及Compose中的大多数容器, 如方框、行、约束布局)都是内联函数. 我们知道, 当我们将内联函数编译到 Java 代码中时, 该函数并不真正存在, 它指示编译器将完整的主体函数带给其调用者, 而无需创建新函数. 这就使得内联可编译函数(如Column)没有自己的重构作用域, 而是遵循父作用域. 这就是为什么在上面的示例中,Column作用域Greeting作用域一起被调用, 因为Column作用域实际上并不存在.

本文的简要说明

内联函数与非内联函数

在对第1部分和第2部分进行抽查后, 我有一个问题:

“如果重组在作用域组件内重组了全部代码, 那么为什么我们不能将每一个组件分块/包裹成更小的作用域来获得更小的重组作用域呢?”

在回答这个问题之前, 让我们跳到第三部分.

第三部分–重构作用域和可跳过组件

Android Studio为Jetpack Compose提供了布局检查器(Layout Inspector), 有了这个布局检查器, 我们就可以跟踪重组的调用次数, 并查看哪些组件被重组或跳过.

使用布局检查器获取重组次数*

Layout Inspector

让我们修改代码示例, 现在我们添加了新的状态和读取状态的组件, 但该状态永远不会发生变化, 让我们看看下面的代码:

@Composable
fun Greeting() {
    var state by remember {
        mutableStateOf("Hi Foo")
    }
    var staticState by remember {
        mutableStateOf("This state never changes")
    }
    LogCompositions(msg = "Greeting Scope")
    Column {
        LogCompositions(msg = "Column Scope")
        Text(text = state)
        Text(text = staticState)
        Button(
            onClick = { state = "Hi Foo ${Random.nextInt()}" },
        ) {
            LogCompositions(msg = "Button Scope")
            Text(
                text = "Click Me"
            )
        }
    }
}

在第6行, 我们有一个名为staticState的状态. 这个状态是可变的, 但永远不会发生变化, 这个状态在第13行被文本组件读取. 因此, 我们有两个状态, 并在同一作用域内被两个文本组件读取. 现在让我们运行上面的代码, 比较LogComposition和Layout Inspector的结果.

LogComposition和布局检查器的结果

我们可以看到, 第一个文本和第二个文本位于同一作用域, 即"Greeting作用域". 每次我们点击按钮时都会调用该Greeting作用域, 但它只会更新state变量, 而不会更新statisState.

请仔细观察我们的布局检查器, 即使调用了Greeting作用域上的全部代码, 金豪也知道第二个文本读取的状态没有更新, 因此金豪直接跳过了重新组成这个组件. 通过跳过不必要的重新编译组件, Compose可以高效地工作.

现在我们知道, 没有必要对重组作用域进行分块或将每个组件封装到更小的重组作用域中, 更重要的是确保重组作用域中的每个组件都是可跳过的, 因此即使在单个重组作用域中存在大量组件, 也不会影响性能, 因为Compose只重组必要的组件, 其他组件则不会重组.

要获得可跳过和不可跳过组件的完整报告, 我们可以使用 Composable衡量指标 或第三方库, 如Mendable.

结论

  • Compose通过调用读取状态的最小重组作用域进行智能重组
  • 与其担心一个作用域中的代码行, 不如确保重组作用域中的组件是可跳过的, 这将提高Compose的工作效率.
  • 一般来说, 在不知道函数是否内联的情况下, 很难确定哪些作用域将被重组, 因此应使用辅助工具(如Layout Inspector)来跟踪重组情况.
  • 出于优化目的, 重组规则可能会在未来的开发中发生变化

为了帮助大家更好的熟知Jetpack Compose 这一套体系的知识点,这里记录比较全比较细致的《Jetpack 入门到精通》(内含Compose) 学习笔记!!! 对Jetpose Compose这块感兴趣的小伙伴可以参考学习下……

Jetpack 全家桶(Compose)

Jetpack 部分

  1. Jetpack之Lifecycle
  2. Jetpack之ViewModel
  3. Jetpack之DataBinding
  4. Jetpack之Navigation
  5. Jetpack之LiveData

Compose 部分
1.Jetpack Compose入门详解
2.Compose学习笔记
3.Compose 动画使用详解

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

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

相关文章

postman入门基础 —— 接口测试流程

一、编写接口测试计划 接口测试计划和功能测试计划目标一致,都是为了确认需求、确定测试环境、确定测试方法,为设计测试用例做准备,初步制定接口测试进度方案。一般来说,接口测试计划包括概述、测试资源、测试功能、测试重点、测试…

数据结构之队列详解(包含例题)

一、队列的概念 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操…

l2a股接口从哪几方面测评?(l2行情接口)

股票数据接口是一种用于获取、传输和处理股票市场相关数据的软件接口。l2a股接口提供了一种连接股票市场数据源和数据使用者之间的通道,允许开发者通过编程方式获取股票行情数据、交易数据和相关信息等。 股票数据接口主要有两种类型:实时行情数据接口和…

SPI协议个人记录

SPI协议 SPI(Serial Peripheral Interface)是一种同步串行接口技术,由Motorola公司推出。SPI总线系统是一种同步串行外设接口,允许MCU与各种外围设备以串行方式进行通信和数据交换。外围设备包括FLASHRAM、A/D转换器、网络控制器…

时序预测-Informer简介

文章目录 Informer介绍1. Transformer存在的问题2. Informer研究背景3. Informer 整体架构3.1 ProbSparse Self-attention3.2 Self-attention Distilling3.3 Generative Style Decoder 4. Informer的实验性能5. 相关资料 Informer介绍 1. Transformer存在的问题 Informer实质…

SysML V1.2 Ports and Flows

概述 本章介绍了流端口,它们允许在块和部件之间传输物品流,以及标准端口,它们允许在块和部件上调用服务。端口是块或部件与其环境之间的交互点,通过连接器与其他端口相连。指定系统元素上的这些端口的主要动机是允许设计具有明确…

【TI毫米波雷达笔记】IWR6843AOP的CCS工程模板创建(DSS)

【TI毫米波雷达笔记】IWR6843AOP的CCS工程模板创建(DSS) 如果还不会配置MSS 则看我的文章: blog.csdn.net/weixin_53403301/article/details/132274755大体上与MSS创建也差不多 SDK我用的3.5版本 DSS工程模板: download.csdn.…

HTML和JavaScript实现一个简单的计算器

使用HTML和JavaScript实现一个简单的计算器。 一、绘制键盘 <!DOCTYPE html> <html> <head><title>Simple Calculator</title><style>.calculator {display: grid;grid-template-columns: repeat(4, 1fr);grid-gap: 5px;padding: 10px;}.…

产品进行逻辑梳理的三个重点

我们在进行产品方案设计时&#xff0c;需要进行逻辑梳理&#xff0c;这样才能保障产品方案的严谨性&#xff0c;降低后期返工几率。如果我们在接到需求时&#xff0c;直接开始画原型&#xff0c;没有经过逻辑梳理&#xff0c;往往容易造成产品方案纰漏丛生&#xff0c;直接影响…

NFTScan | 08.07~08.13 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。周期&#xff1a;2023.08.07~ 2023.08.13 NFT Hot News 01/Magic Eden 已上线 BRC-20 小数铭文与买卖 8 月 7 日&#xff0c;NFT 市场 Magic Eden 发推称&#xff0c;BRC-20 小数铭文与买卖现已上线&…

CentOS8安装Git

错误1. 执行yum命令报错 【错误&#xff1a;Invalid configuration value: failovermethodpriority in /etc/yum.repos.d/CentOS-epel.repo; 配置&#xff1a;ID 为 "failovermethod" 的 OptionBinding 不存在】 1.cd /etc/yum.repos.d 2.vim CentOS-epel.repo //…

计算机竞赛 opencv 图像识别 指纹识别 - python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于机器视觉的指纹识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c;适…

Python实现SSA智能麻雀搜索算法优化BP神经网络回归模型(BP神经网络回归算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 麻雀搜索算法(Sparrow Search Algorithm, SSA)是一种新型的群智能优化算法&#xff0c;在2020年提出&a…

布隆过滤器原理及应用

使用场景 适用于数据命中不高、 数据相对固定、 实时性低&#xff08;通常是数据集较大&#xff09; 的应用场景。比如&#xff1a; 解决缓存穿透&#xff1b;爬虫时记录已爬取的网页&#xff1b;记录黑名单&#xff1b; 原理 数据结构是一个bit数组&#xff0c;布隆过滤器…

Rx.NET in Action 中文介绍 前言及序言

Rx 处理器目录 (Catalog of Rx operators) 目标可选方式Rx 处理器(Operator)创建 Observable Creating Observables直接创建 By explicit logicCreate Defer根据范围创建 By specificationRangeRepeatGenerateTimerInterval Return使用预设 Predefined primitivesThrow …

【vue】alert弹窗太死板?试试这种方法(附代码)

alert(response.data.message); 新方法&#xff1a; this.$message.error(请检查您输入的的用户名和密码&#xff01;);

等保案例 2

用户简介 青海省司法厅是贯彻执行国际司法行政的方针、政策&#xff0c;拟定青海市司法行政工作的政策、法规的重要组织机构。青海省司法厅根据国家部署及业务需要先后简历了司法专网及行政办公网&#xff0c;目前两套网络之间完全物理隔离。但青海省司法厅现有网络设备单一&a…

学会这个小技巧,让你的Jenkins更好用

♥ 前 言 在使用 Jenkins 时&#xff0c;我们经常遇到这样的需求&#xff1a;在 Jenkins 构建的时候拉取指定分支的代码或者指定 tag 的代码&#xff0c;而 Jenkins 默认没有带这样的参数化选择功能&#xff0c;我们可以通过 Git Parameter 插件来实现。 一、准备&#xff…

公文管理系统SSM+Activiti文档文件日志java jsp源代码

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 公文管理系统SSMActiviti 系统有1权限&#xff1a;管…

达摩院FunASR实时语音听写服务软件包发布

7月初&#xff0c;FunASR社区发布了离线文件转写软件包&#xff0c;可以高精度、高效率、高并发的支持长音频离线文件转写&#xff0c;吸引了众开发者参与体验。应开发者需求&#xff0c;FunASR社区再次推出实时语音听写服务软件包&#xff0c;支持实时地进行语音转文字&#x…