Android Jetpack Compose之确定重组范围并优化重组

news2024/11/28 10:54:43

目录

  • 1.概述
  • 2.确定Composable重组的范围
  • 3.优化重组的性能
    • 3.1 Composable 位置索引
    • 3.2 通过Key添加索引信息
    • 3.3 使用注解@Stable优化重组

1.概述

前面的文章提到Compose的重组是智能的,Composable函数在进行重组时会尽可能的跳过不必要的重组,只对需要变化的UI进行重组。那Compose是如何认定UI需要变化呢?或者换句话说Compose是如何确定重组的范围呢。如果重组随意的发生,那么对UI的性能会是一个很不稳定的状态,时而好,时而坏。而且如果编写的UI代码有问题,那么重组将会带来状态的混乱,导致UI显示出错。所以弄清楚Compose重组的范围确定才能更好的避免重组的坑,并且可以针对具体的范围做优化,所以本文将介绍如何确定Compose重组的范围以及重组性能的优化。

2.确定Composable重组的范围

确定重组的范围有助于我们更好的理解ComposeUI的性能优化,下面我们先看一个例子:

    @Composable
    fun CounterDemo(){
        Log.d("zhongxj","范围1=>运行")
        var counter by remember { mutableStateOf(0) }
        Column {
            Log.d("zhongxj","范围2=>运行")
            Button(onClick = {
                Log.d("zhongxj","onButtonClick:点击按钮")
                counter ++
            }){
                Log.d("zhongxj","范围3=>运行")
                Text(text = "+")
            }

            Text(text = "$counter")
        }
    }

在上面的代码中,我们依然使用计数器的例子来验证重组的范围,我们在各个可能发生重组的地方都打上了Log,当点击Button时,计数器counter的状态更新会触发CounterDemo的重组,日志如下图所示:
在这里插入图片描述

从图中我们可以看到, Log.d("zhongxj","范围3=>运行") 这行Log并没有打,没有打这行log的原因需要我们了解Compose重组的底层原理:

在Compose中,经过Compose编译器处理后的Composable函数在对State进行读取的同时,能够自动建立关联,在运行过程中,当State变化时Compose会找到关联的代码块并将其标记为Invalid.在下一个渲染帧到来之前,Compose会触发重组并且执行invalid代码块,而Invalid代码块即为下一次重组的范围。能够被标记为Invalid的代码有2个两个要求,一是被标记为Invalid的代码必须时非inline且没有返回值的Composable函数,二是无返回值的Lambda。

那么为啥参与重组的代码块必须是非inline的无返回值函数呢?因为inline函数在编译期会在调用处展开,因此无法在下次重组时找到合适的调用入口,只能共享调用方的重组范围。而有返回值的函数由于返回值会影响调用方,所以必须联通调用方一起参与重组。因此inline的有返回值的函数不能作为Invalid代码块。

而了解了Compose的底层重组原理,我们就可以清楚的知道了只有受到State变化影响的代码块,才会参与到重组。不依赖State的代码则不参与重组,这就是重组的最小化原则。

基于重组最小化原则,我们可以分析下我们计数器例子中的输出结果,其实看了日志发现 Log.d("zhongxj","范围3=>运行")这行日志没有打,也就是说这行日志所在的代码块并没有参与重组,在范围2的作用域中,我们看到了这行代码Text(text = "$counter"),很明显这行代码依赖了counter状态,需要注意的是这行代码并不是读取counter值的意思,它的意思是在范围2的作用域中读取counter的值并传入Text,所以范围2是会参与重组的,日志就输出了Log.d("zhongxj","范围2=>运行"),这时有读者可能会发现,按照重组最小化原则,那么访问counter的最小范围应该是:范围2的作用域呀,为啥范围1的日志也会被打印呢?这里需要回想下咱们之前讲的:最小化范围的定义必须是非inline的composable函数或者lambda。而Column组件是一个inline声明的高阶函数:
在这里插入图片描述所以content内部也会被展开在调用处,所以范围1和范围2就共享了重组的范围,所以输出了Log.d("zhongxj","范围1=>运行")日志,假设将Column换成非inline的Composable,那么Log.d("zhongxj","范围1=>运行")将不会输出,比如换成一个Card组件,读者可以自行试一下。

需要注意的是,Button虽然没有依赖counter,但是范围2的重组会触发Button的重新调用,所以 Log.d("zhongxj","onButtonClick:点击按钮") 也会输出,但是其content内部并没有依赖counter,所以范围3的日志: Log.d(“zhongxj”,“范围3=>运行”) 不会输出。

补充说明: Composable 函数观察State变化并触发重组是在被称为”快照“的系统中完成的,所谓”快照“就是将被访问的状态像拍照一样保存下来,当状态变化时,通知相关的Composable应用的最新状态。”快照“有利于对状态管理进行线程隔离,在多线程场景下的重组有重要的应用

3.优化重组的性能

经过前面的分析,我没了解到了Compose的重组是智能的,遵循范围最小化原则,重组中执行到的Composable只有在其参数发生变化时,才会参与本次重组。

Compose 在执行后会生成一棵视图树,每个Composable对应树上的一个节点,因此Composable 智能重组的本质其实是从树上寻找对应位置的节点并与之进行比较,如果节点未发生变化则不用更新

另外需要注意的是,视图树的实际构建过程比较复杂,Composable执行过程中,先将生成的Composition状态存入SlotTable,然后框架基于SlotTable生成LayoutNode树,并完成最终的界面渲染。所以谨慎的说,Composable的比较逻辑是发生在SlotTable中的。

3.1 Composable 位置索引

在重组的过程中,Composition上的节点可以完成增、删、移动、更新等多种变化,Compose编译器会根据代码调用位置,为Composable生成索引key,并且存入Composition,Composable在执行过程中通过与Key的对比可以知道当前应该执行何种操作。例如下面的示例代码:

    Box {
            if (state) {
                val str = remember(Unit) { "call_site_1" }
                Text(text = str) // Text_of_call_site_1
            } else {
                val str = remember(Unit) { "call_site_2" }
                Text(text = str) // Text_of_call_site_2
            }
        }

如上面代码所示:Composable中遇到if/else等条件语句时,会插入startXXXGroup类似的代码,并且通过添加索引Key识别节点的增减,上面的代码中会根据state的不同显示不同的Text,编译器会为if和else分支分别建立索引,当state由true变为false时,Box发生重组,通过key的判断可知,else内的代码需要插入逻辑执行,而if内生成的节点需要被移除。

假设没有编译期的位置索引,而仅仅靠运行时比较,首先执行到 remember(Unit)时,由于缓存原因仍然会返回当前树上存放的str,即call_site_1,接着执行到Text_of_call_site_1,发现与当前树上的节点类型一样,参数str也没有变化,因此会判断为无须重组,那么文本就无法得到更新

所以,综上所述:Composable 在编译期建立索引是保证其重组能够智能且正确执行的基础。这个索引是根据Composable在静态代码中的被调用位置决定的。但是在某些场景中,Composable无法通过静态代码位置进行索引,这时我们需要手动添加索引,便于在重组中进行比较

3.2 通过Key添加索引信息

假设我们现在需要给一个电影列表,然后展示电影的大致信息,代码如下所示:

@Composable
    fun MoviesScreen(movies:List<Movie>){
        Column { 
            for (movie in movies){
                // showMoveCardInfo 无法在编译期间进行索引,只能根据运行时的index进行索引
                showMoveCardInfo(movie)
            }
        }
    }

如上面的代码所示,基于Movie的名字展示电影的信息,此时无法基于代码中的位置进行索引,只能在运行时基于index进行索引。这样的话索引会根据item的数量发生变化,导致无法准确进行比较。在这种情况下,当重组发生时,新插入的数据会和以前的第一个数据比较,以前的第一个数据会和第二个数据比较,然后以前的第二个数据会被当作新数据插入。结果是所有的item都会发生重组,但是我们期望的行为是,只有新插入的数据需要重组,其他没有变化的数据不应该发生重组,所以我们可以使用key的方法为Composable在运行时手动添加一个索引,如下所示:

@Composable
    fun MoviesScreen(movies:List<Movie>){
        Column { 
            for (movie in movies){
               key(movie.id){ // 使用movie的唯一ID作为Composable的索引
                showMoveCardInfo(movie)
                }
            }
        }
    }

使用movie的ID传入Composable做为唯一索引,当插入新数据时,之前对象的索引没有被打乱,仍然可以发挥比较时的锚定作用,所以其他没有发生变化的item就可以不用参与重组

3.3 使用注解@Stable优化重组

Composable是基于参数的比较结果来决定是否重组,也就是说,只有当参与比较的参数对象是稳定的且equals返回true,才认为是相等的。Kotlin中常见的基本类型(Boolean、Int、Long、Float、Char) String,Lambda表达式都可以认为式稳定的,因为都是不可变类型。所以他们的参数比较的结果都式可信的。但是假如参数是可变类型,那么比较的结果将是不可信的。

data class Mutabledata(var data:String)
    @Composable
    fun MutableDemo(){
        var mutable = remember { Mutabledata("walt") }

        var state by remember { mutableStateOf(false) }
        if(state){
            mutable.data = "zxj"
        }

        Button(onClick = {state = true}){
           showText(mutable)
        }
    }
    @Composable
    fun ShowText(mutable:MutableData){
     Text(text = mutable.data) // 会随着state的变化而变化
    }

在上面的代码中,MutableData是一个不稳定的对象,因为它有一个Var类型的变量data,当点击按钮改变状态时,mutable会修改data,对于ShowText来说,参数mutable在状态改变前后都指向同一个对象,因此仅仅靠equals判断会认为参数没有发生变化,但实际上测试发现ShowText函数发生了重组,所以Mutabledata参数类型是不稳定的,equals结果不可信。

所以对于一些默认不被认为是稳定类型的,比如interface或者list等集合类,如果能够确保其在运行时的稳定,可以为其添加@State注解,编译器会将这些类型视为稳定类型,从而发挥只能重组的作用,提升性能。代码如下所示:

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

注意: 被添加为@Statble的普通父类、密封类、接口等其派生子类也会被认为时稳定的

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

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

相关文章

C语言中结构体,枚举,联合相关介绍

本次重点&#xff1a; 1、结构体 &#xff1a; &#xff08;1&#xff09;结构体类型的声明 &#xff08;2&#xff09;结构的自引用 &#xff08;3&#xff09;结构体变量的定义和初始化 &#xff08;4&#xff09;结构体内存对齐 &#xff08;5&#xff09;结构体传参 …

干细胞液氮容器选择与使用

干细胞液氮容器的使用非常重要&#xff0c;以确保干细胞样品在冷冻和储存过程中的有效性和安全性。以下是使用干细胞液氮容器时需要注意的事项&#xff1a; 1、容器选择&#xff1a;选择合适的容器非常重要。容器应具有良好的密封性能和耐腐蚀性&#xff0c;以避免外部空气和污…

01-Zookeeper特性与节点数据类型详解

上一篇&#xff1a; 在了解Zookeeper之前&#xff0c;需要对分布式相关知识有一定了解&#xff0c;什么是分布式系统呢&#xff1f;通常情况下&#xff0c;单个物理节点很容易达到性能&#xff0c;计算或者容量的瓶颈&#xff0c;所以这个时候就需要多个物理节点来共同完成某项…

Oracle VM VirtualBox安装并下载安装CentOS7

Oracle VM VirtualBox安装并下载安装CentOS7 Oracle VM VirtualBox下载CentOS创建虚拟机 Oracle VM VirtualBox VM下载链接 https://www.oracle.com/cn/virtualization/virtualbox/ 点击链接直接下载就行&#xff0c;下载完默认安装或者更改一下安装目录。 下载CentOS http://…

服务网格概述

引言 2016 年前后&#xff0c;"服务网格"这个词出现在微服务、云计算和 DevOps 的领域。Buoyant 团队在 2016 年用这个词来解释他们的产品 Linkerd。服务网格的到来主要是由于 IT 领域内的一场风暴。开发人员开始使用多语言&#xff08;polyglot&#xff09;方法构建…

古彝文识别:文化遗产的数字化之旅

目录 &#x1f345;前言&#x1f353;古彝文介绍&#x1f353;古彝文识别的重难点&#x1f352;原籍难以获取&#xff0c;传统翻译过程繁琐&#xff0c;周期长。&#x1f352;版式多样&#xff0c;笔画相近。&#x1f352;图像质量差&#xff0c;手写识别难。&#x1f352;古彜…

第二证券:迎政策助力,新型工业化爆发,德恩精工3日涨超60%

新式工业化概念26日盘中大幅拉升&#xff0c;到发稿&#xff0c;德恩精工、精伦电子、天永智能等涨停&#xff0c;固高科技涨约8%&#xff0c;亚威股份涨逾6%&#xff0c;金自天正、创世纪涨约5%。 值得注意的是&#xff0c;精伦电子已接连5个交易日涨停&#xff0c;公司昨日晚…

Mac菜单栏图标管理工具:Bartender 5 完美兼容MacOS Sonoma 14系统

Bartender 5 是一款流行的软件程序&#xff0c;专为酒店行业的调酒师和专业人士设计。它提供了一系列功能和工具来简化酒吧或餐厅的饮料订单、库存和客户偏好的管理流程。Bartender 5 的一些主要功能包括&#xff1a; 1. 饮料配方&#xff1a;该软件包括一个全面的饮料配方数据…

计算机丢失msvcp140_1.dll的解决办法,丢失msvcp140_1.dll的原因

丢失 msvcp140_1.dll 是一个常见的错误信息&#xff0c;通常会在尝试运行某些程序时出现。msvcp140_1.dll 是一个动态链接库文件&#xff0c;它包含了许多 C标准库函数的实现&#xff0c;这些函数在许多程序中都是必需的。因此&#xff0c;如果丢失了该文件&#xff0c;程序可能…

【MySQL基础 | 中秋特辑】多表查询详细总结

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【MySQL学习专栏】&#x1f388; 本专栏旨在分享学习MySQL的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 目录 一、多表…

如何解决跨浏览器兼容性问题?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 解决跨浏览器兼容性问题⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量…

台式多参数水质测定仪可以测哪些污水

台式污水测定仪可以测哪些污水&#xff1a; 1.废水处理厂监测&#xff1a;废水处理厂需要定期监测废水中的污染物浓度&#xff0c;包括COD、氨氮、总磷和总氮等指标。台式废水检测仪可以提供快速、准确的检测结果&#xff0c;帮助废水处理厂了解废水处理效果&#xff0c;以便进…

大数据Flink(八十七):DML:Joins之Regular Join

文章目录 DML:Joins之Regular Join DML:Joins之Regular Join Flink 也支持了非常多的数据 Join 方式,主要包括以下三种: 动态表(流)与动态表(流)的 Join动态表(流)与外部维表(比如 Redis)的 Join动态表字段的列转行(一种特殊的 Join)细分 Flink SQL 支持的

《学术小白学习之路》DTM主题动态模型构建

《学术小白学习之路》DTM主题动态模型构建 一、LDA与DTM的区别二、代码实操2.1 数据2.2 获取数据向量2.3 参数设置与模型构建2.4 结果的输出一、LDA与DTM的区别 LDA主题模型主要针对一段段的文档 可以得出每个主题,所对应主题词的词语的概率 该模型的主题概率的生成是基于文…

泰国数字加密平台Bitkub创始人到访上海和数集团

2023年9月21日&#xff0c;泰国数字加密货币交易平台Bitkub创始人兼首席执行官&#xff08;CEO&#xff09;Jirayut Srupsrisopa (Topp)先生到访上海和数集团总部。董事长唐毅先生热情会见了来宾&#xff0c;双方进行了友好深入的交流。 和数集团国际部经理晋松&#xff1b;苏州…

【Linux】计算机的软硬件体系结构

文章目录 一、冯诺依曼体系结构二、操作系统(Operator System)1.操作系统的概念2.为什么要有操作系统3.操作系统如何进行管理 三、系统调用和用户操作接口1.系统调用接口2.用户操作接口 四、计算机的软硬件体系结构 一、冯诺依曼体系结构 目前我们常见的计算机&#xff0c;如笔…

自洽可分的哈密顿系统的辛算法

本文只介绍哈密顿系统的辛算法的显式结构 不给出具体的推导过程 自洽可分的哈密顿系统的辛算法 一阶显式辛结构 二阶显式辛结构 四阶显式辛结构 全代码 import matplotlib.pyplot as plt import numpy as np from scipy.optimize import fsolve##SymplecticHamilton ##self-c…

Room Arranger for Mac: 轻松创造梦想家园的必备设计软件

你是否曾经梦想过自己动手设计理想中的家居环境&#xff1f;你是否希望通过一个简单易用的工具来实现你的设计理念&#xff1f;那么&#xff0c;Room Arranger for Mac就是你的最佳选择&#xff01; Room Arranger是一款专门为Mac用户打造的室内设计软件&#xff0c;它拥有直观…

软件测试面试题 —— 整理与解析(4)

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

搭建SpringBoot项目三种方式(超详细版)

目录 一、官网下载压缩包解压 二、通过Idea脚手架搭建 三、Spring Boot项目结构 3.1 pom.xml文件 3.2 启动类 3.3 配置文件 四、通过创建Maven项目添加依赖 一、官网下载压缩包解压 接下来我们搭建一个SpringBoot项目&#xff0c;并引入SpringMVC的功能&#xff0c;首先…