Jetpack Compose 1.5 发布:全新 Modifier 系统助力性能提升

news2024/12/28 4:38:32

不久前 Compose 1.5.0 稳定版发布,在组合的性能方面得到明显改善,这主要归功于对 Modifier API 的持续重构。

Modifier 是 Compose 中的重要概念,为 Composition 中的 LayoutNode 配置各种样式信息以用于后续渲染。在 1.3.0 之前的 Modifier 在实现机制上存在性能问题,从 1.3.0 起 Modifier 系统得到重构,通过 Modifier.Node 的引入,性能大幅提升。从 1.3.0 起,既有的 Modifier 操作符逐渐重构到新的架构。

1.3.0 之前的 Modifier

通常,我们会为 Composable 像 modifier = Modifier.xxx 这样添加多个 Modifier 操作符

@Composable
fun Box(
    modifier = Modifier
        .background(..)
        .size(..)
        .clickable(..)
)

操作符内部会通过 composed {} 生成 ComposedModifier

以下以 .clickable() 为例:

fun Modifier.clickable(..onClick: () -> Unit): Modifier = composed {
    val onClickState = rememberUpdatedState(onClick)
    val pressedInteraction = remember { mutableStateOf<PressInteraction.Press?>(null) }
    ..

    return this
        ..
        .composed { remember { FocusRequesterModifier() } } 
}

ComposedModifier 会通过 Composer.materialize() 进一步展开 Modifier.Element 的链式结构,在 Element 构建过程中(factory 调用),remember { } 将必要的状态作为 LayoutNode 的关联信息存入 Composition

/** 
* Materialize any instance-specific [composed modifiers][composed] for applying to a raw tree node. 
* Call right before setting the returned modifier on an emitted node. 
* You almost certainly do not need to call this function directly. 
*/

@Suppress("ModifierFactoryExtensionFunction")
fun Composer.materialize(modifier: Modifier): Modifier {
     val result = modifier.foldIn<Modifier>(Modifier) { acc, element ->
          acc.then(
              val factory = element.factory as Modifier.(Composer, Int) -> Modifier
              val composedMod = factory(Modifier, this, 0)
              materialize(composedMod)
          )
          ..
     }

     return result
}

composed {}Composer.materialize() 的初衷是可以 Just-In-Time 地为不同 LayoutNode 生成不同的状态。即使是共用同一个 Modifier ,例如下面这样的 case ,虽然共用 m,但是 Content1onClick 带来的水波纹效果不应该影响到 Content2

@Composable
fun App() {
    val m = Modifier
        .padding(10.dp)
        .background(Color.Blue)
        .clickable {...}
    Row {
        Content1(m)
        Content2(m)
    }
}

但是,这样的机制也带来了性能问题。Modifier 现场展开 Modifier.Element 数量远多于表面上看到的操作符数量,这个过程会造成大量内存的开销和更高的 GC 概率。而且 Modifier 的调用处不在 Composable 中,无法智能的跳过重组,每次重组都会执行。

Modifier.Node 的引入

解决上述问题的思路,一是减少 Modifier.Element 的创建数量,二是让 Modifier 的状态也可以参与比较,更智能地重组。 因此,Compose 引入了 Modifier.Node

interface Modifier {
    /**     
    * The longer-lived object that is created for each [Modifier.Element] applied to a     
    * [androidx.compose.ui.layout.Layout]..     
    */
    abstract class Node : DelegatableNode, OwnerScope {
        final override var node: Node = this
        private setinternal var parent: Node? = null
        internal var child: Node? = null
        
        ..
    }
}

新的系统下,每个 Modifier 操作符不会再生成过多的 Modifier.Element,只会生成一一对应的 Modifier.Node

总体来说 Modifier.Node 带来三个好处:

  • 更少的分配: 生成的 Element 的数量大大降低,避免了内存抖动和内存占用。

  • 更轻量的树:状态存在 Node 上,不再依靠 remember {} 存储,Composition 的节点数也随之减少,树的遍历速度也更快了。

  • 更快的重组:Modifier.Node 为重组提供可比较标的物, 非必要不重新生成 Node,重组性能得到提升。

迁移到新的 Modifier 系统

Compose 预置的 Modifier 操作符会逐渐迁移到新系统,对于上层使用没有影响,对于自定义 Modifier 如何向新系统迁移呢?以下以 roundRectangle 举例,这个操作符用来绘制带颜色的圆角矩形

// Draw round rectangle with a custom color
fun Modifier.roundRectangle(
    color: Color
) = composed {
    // when color changed,create and return new RoundRectanleModifier instance
    val modifier = remember(color) {
        RoundRectangleModifier(color = color)
    }
    return modifier
}

// implement DrawModifier 
class RoundRectangleModifier(
    private val color: Color,
): DrawModifier {
    override fun ContentDrawScope.draw() {
        drawRoundRect(
            color = color,
            cornerRadius = CornerRadius(8.dp.toPx(), 8.dp.toPx())
        )
    }
}

调用效果如下:

val color by animateColorAsState(..)

Box(modifier = Modifier..roundRectangleNode(color = color)) { .. }

Step 1 :定义 Modifier.Node

首先,创建 RoundRectangleModifierNode ,实现自 Modifier.Node,,同之前的 RoundRectangleModifier 签名保持一致即可

@OptIn(ExperimentalComposeUiApi::class)
class RoundRectangleModifierNode(
    var color: Color,
): Modifier.Node()

注意参数 color 用 var 而非 val,理由稍后说明

Step 2:实现 DrawModifierNode

第二步为 Modifier 提供原本的绘画能力,需要添加接口 DrawModifierNode,并像 DrawModifier 一样重写 ContentDrawScope.draw 方法:

class RoundRectangleNodeModifier(
    var color: Color,
): DrawModifierNode, .. {
    override fun ContentDrawScope.draw() {
        drawRoundRect(
            color = color,
            cornerRadius = CornerRadius(8.dp.toPx(), 8.dp.toPx())
        )
    }
}

这个 DrawModifierNode 实现自 DelegatableNode,后者是对 Modifier.Node 的包装,顾名思义起到代理的作用

/** 
* Represents a [Modifier.Node] which can be a delegate of another [Modifier.Node]. Since 
* [Modifier.Node] implements this interface, in practice any [Modifier.Node] can be delegated. 
*/
@ExperimentalComposeUiApi
interface DelegatableNode {
    val node: Modifier.Node
}

interface DrawModifierNode : DelegatableNode {
    fun ContentDrawScope.draw()
    ..
}

类似的代理类还有很多

/** ..
 * @see androidx.compose.ui.node.LayoutModifierNode
 * @see androidx.compose.ui.node.DrawModifierNode
 * @see androidx.compose.ui.node.SemanticsModifierNode
 * @see androidx.compose.ui.node.PointerInputModifierNode
 * @see androidx.compose.ui.node.ParentDataModifierNode
 * @see androidx.compose.ui.node.LayoutAwareModifierNode
 * @see androidx.compose.ui.node.GlobalPositionAwareModifierNode
 * @see androidx.compose.ui.node.IntermediateLayoutModifierNode
 */
 @ExperimentalComposeUiApi
 abstract class Node : DelegatableNode, OwnerScope { .. }

Step 3:创建 ModifierNodeElement

新系统下,Element 生成的数量会大大减少,但并非没有。我们仍然需要创建 Node 数量匹配的 Element,即 ModifierNodeElement 。它需要在重组中参与 Node 比较的。Modifier.roundRectangle 的定义的核心就是使用 modifierElementOf 构建 ModifierNodeElement 。

@OptIn(ExperimentalComposeUiApi::class)
fun Modifier.roundRectangle(
    color: Color
) = this then modifierElementOf(
        params = color,
        create = { RoundRectangleModifierNode(color) },
        update = { currentNode -> currentNode.color = color },
        definitions = ..
    )

这里的几个关键参数比较好懂,简单说明如下:

  • param : 标识 Modifier 是否变化的参数 。当该参数发生变化时,将调用 update 回调函数。类似于前面 RoundRectangleModifier 的例子中的 remember 的用途

  • create: 用于创建新的 Modifier.Node ,我们可以在此处编写 Modifier.Node 的初始化逻辑。

  • update: 用于更新 Modifier.Node 实例。当前的 Modifier.Node 实例将作为参数传递给此回调函数,并返回更新后的 Modifier.Node。在这里,可以重新设置颜色( 为什么设置成 var ),并在下次绘制时使用更新后的颜色

最新进展 (截止到 2023/08)

众所周知,Compose 库自底向上分为多层,Modifier API 重构也是在自底向上稳步进行,目前 Compose UI 的全部 和 Compose Foundation 中的低级别 Modifier API 都完成了迁移,开始向更多高级 Modifier API 覆盖。1.5.0 中覆盖率进一步扩大,Clickable 这样的常用 API 也完成了迁移,因此才带了明显的性能提升,在某些情况下甚至提高了 80%,预计不久将来 Modifier 完成全部迁移时, Compose 的性能定会上一个新台阶。

参考

  • https://www.youtube.com/watch?v=BjGX2RftXsU
  • https://goodpatch-tech.hatenablog.com/entry/modifier-node-summary
  • https://android-developers.googleblog.com/2023/08/whats-new-in-jetpack-compose-august-23-release.html

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

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

相关文章

Java知识点二

Java知识点二 1、Comparable内部比较器&#xff0c;Comparator外部比较器2、源码结构的区别:1&#xff09;Comparable接口&#xff1a;2&#xff09;Comparator接口&#xff1a; 2、Java反射 1、Comparable内部比较器&#xff0c;Comparator外部比较器 我们一般把Comparable叫…

【MySQL】表的增删改查

目录 MySQL表的增删查改 Create 单行数据全列插入 多行数据指定列插入 插入否则更新 替换数据 Retrieve SELECT 列 全列查询 指定列查询 查询字段为表达式 为查询结果指定别名 结果去重 WHERE 条件 查询英语不及格的同学及其英语成绩 查询语文成绩在80到90分的…

【自动化测试】如何在jenkins中搭建allure

相信大家在做自动化测试过程中&#xff0c;都会用到自动化测试环境&#xff0c;目前最常见的就是通过容器化方式部署自动化测试环境&#xff0c;但对于一些测试小白&#xff0c;不是很会搭建持续集成环境&#xff0c;特别是从0-1的过程&#xff0c;需要自行搭建很多依赖环境&am…

完全保密的以太坊交易:Aztec网络的隐私架构

1. 引言 Aztec为隐私优先的以太坊zkRollup&#xff1a;即其为具有完全隐私保护的L2。 为了理解私有交易的范式变化性质&#xff0c;以及为什么将隐私直接构建到网络架构中很重要&#xff0c;必须首先讨论为什么以太坊不是私有的。 2. 以太坊&#xff1a;公有链 以太坊为具有…

设计师常用的8款作图软件推荐

在数字时代&#xff0c;绘图软件已经成为设计师不可缺少的工具。从图形设计到插图&#xff0c;从传统绘图到人工智能绘画&#xff0c;为了实现高效、方便、创意的设计&#xff0c;设计师需要一个强大的绘图软件。本文将介绍8个易于使用的绘图软件&#xff0c;每个软件都具有独特…

ME51N 采购申请屏幕增强仅显示字段

1、业务需求 通过委外工单生成的采购申请&#xff0c;需要将自定义“图号”字段显示在采购申请中&#xff0c;且只用于显示即可 2、增强实现 增强表EBAN的结构CI_EBANDB 增强点CMOD&#xff1a;MEREQ001 出口EXIT_SAPLMEREQ_001 首先在TOP文件中引入全局CI_EBANDB 创建子屏…

动态渲染 echarts 饼图(vue 2 + axios + Springboot)

目录 前言1. 项目搭建1.1. 前端1.2. 后端 2. 后端数据渲染前端2.1 补充1&#xff1a;在 vue 中使用 axios2.2. 补充2&#xff1a;Springboot 处理跨域问题2.3. 修改前端代码2.3.1 修改饼图样式2.3.2 调用后台数据渲染饼图2.3.3 改造成内外两个圈 前言 因为上文中提到的需求就是…

轻松上手Three.js:JavaScript 3D库指南

1.Three.js概述 Three.js是使用JavaScript语言编写的一款运行在浏览器中的3D引擎。与WebGL不同&#xff0c;开发人员在使用Three.js进行开发时&#xff0c;无须掌握高深的图形学知识&#xff0c;只需使用少量JavaScript代码即可创建出一个3D场景。可以说&#xff0c;Three.js的…

恒运资本:小盘股的优点?投资小盘股要注意哪些方面?

股市是一个充溢时机和危险的当地&#xff0c;不同出资者有不同的偏好&#xff0c;有的人喜爱追逐大盘蓝筹股&#xff0c;有的人则钟情于小盘股。那么小盘股的长处&#xff1f;出资小盘股要注意哪些方面&#xff1f;恒运资本也为我们准备了相关内容&#xff0c;以供参考。 小盘股…

vue+springboot+mysql的垃圾分类管理系统

1、引言 设计结课作业,课程设计无处下手&#xff0c;网页要求的总数量太多&#xff1f;没有合适的模板&#xff1f;数据库&#xff0c;java&#xff0c;python&#xff0c;vue&#xff0c;html作业复杂工程量过大&#xff1f;毕设毫无头绪等等一系列问题。你想要解决的问题&am…

六、Hive数据仓库应用之Hive事务(超详细步骤指导操作,WIN10,VMware Workstation 15.5 PRO,CentOS-6.7)

Hive远程模式部署参考&#xff1a; 一、Hive数据仓库应用之Hive部署&#xff08;超详细步骤指导操作&#xff0c;WIN10&#xff0c;VMware Workstation 15.5 PRO&#xff0c;CentOS-6.7&#xff09; 文章目录 一、事务的设计与特点1、事务的特点2、事务的设计3、事务的实现 二、…

【LeetCode刷题笔记】动态规划 — 70.爬楼梯

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 更多算法知识专栏&#xff1a;算法分析&#x1f525; 给大家跳段街舞感谢…

Python不是一种伟大的编程语言?

作为一门简洁易用、生态蓬勃且具有高泛用性的编程语言&#xff0c;Python一直以来都被不少人称作“编程语言中的瑞士军刀”。 尤其随着近来AI热潮席卷全球&#xff0c;Python在编程语言圈中的地位也随之水涨船高&#xff0c;甚至一度被视作AI专用语言或大数据专用语言。 然而…

Netty核心原理:一、基础入门-04:NettyServer字符串解码器

文章目录 一、前言介绍二、代码实现2.1 工程结构2.2 Netty服务端字符串解码器实现2.2.1 服务端处理器2.2.2 通道初始化2.2.3 netty服务端 2.3 单元测试 一、前言介绍 &#x1f4a1; 服务端接收数据后我们希望是一个字符串或者是一个对象类型,而不是字节码。 在 netty 中是否可以…

数据结构与算法基础-学习-33-归并排序

目录 一、基本思想 二、算法思路 1、合并两个有序序列 2、分治法 三、算法源码 1、MergeSortTwoSortData 2、TwoWayMergeSortRecurtionSentryQueue 四、算法效率分析 五、Linux环境编译测试 六、小感慨 排序的其他相关知识点和源码分享可以参考之前的博客&#xff1a…

解决 Android 依赖冲突

解决办法 问题原因就是&#xff0c;各个模块所有的依赖&#xff08;递归&#xff09;的 jar 包最后都会加载到安卓的项目中&#xff0c;你可以选择 project 形式查看 External Libraries&#xff0c;都在这了。所以解决问题关键就是干掉冲突&#xff0c;剩下一个就行了&#xf…

将Apache服务与内网穿透结合,让您的网站可以公网访问

Apache服务安装配置与结合内网穿透实现公网访问 文章目录 Apache服务安装配置与结合内网穿透实现公网访问前言1.Apache服务安装配置1.1 进入官网下载安装包1.2 Apache服务配置 2.安装cpolar内网穿透2.1 注册cpolar账号2.2 下载cpolar客户端 3. 获取远程桌面公网地址3.1 登录cpo…

2023年中国电影行业研究报告

第一章 行业概况 1.1 定义 电影行业是一门涉及电影制作、发行、放映和推广的综合艺术和商业活动。它结合了戏剧、音乐、舞蹈、绘画等多种艺术形式&#xff0c;通过视觉和听觉的方式向观众展示故事和情感。 电影不仅仅是一门艺术&#xff0c;更是一项复杂的商业运作。它涵盖了…

element el-input 二次封装

说明&#xff1a;为实现输入限制&#xff0c;不可输入空格&#xff0c;长度限制。 inputView.vue <template><!-- 输入框 --><el-input:type"type":placeholder"placeholder"v-model"input"input"inputChange":maxle…

短信轰炸漏洞绕过的多种方法技巧

前言&#xff1a; 在测试甲方业务或者挖 SRC 等业务的时候&#xff0c;经常碰到发送短信验证的地方&#xff0c;我们可以联想到的就是任意用户登陆、短信轰炸、任意用户修改密码等逻辑性的漏洞&#xff0c; 简单的漏洞也是需要清晰的思维分析&#xff0c;拿几个短信轰炸多个绕…