Jetpack Compose 1.5 发布:全新 Modifier 系统带来性能大幅提升

news2025/1/16 1:58:46

不久前 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/998405.html

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

相关文章

【教师节特辑】做个教师节快乐照片墙吧

写作原因&#xff1a; 教师节到了&#xff0c;身边或多或少都有很多不少的老师&#xff0c;基本以前认识的老师都不记得了&#xff0c;以后总也会认识一些日本老师的。程序员&#xff0c;就应该以自己的方式来庆祝教师节。想了下&#xff0c;要不还是做个照片墙把。 项目链接 …

2024年java面试--mysql(2)

系列文章目录 2024年java面试&#xff08;一&#xff09;–spring篇2024年java面试&#xff08;二&#xff09;–spring篇2024年java面试&#xff08;三&#xff09;–spring篇2024年java面试&#xff08;四&#xff09;–spring篇2024年java面试–集合篇2024年java面试–redi…

电商API与电商数据经济的产生【电商平台-淘宝/京东/拼多多下的API数据经济】

计算机连接了互联网后&#xff0c;释放出了巨大的创新力和价值&#xff0c;同样地&#xff0c;智能合约一旦连接到快速增长的链下数据和API经济&#xff0c;也将变得无比强大。如果智能合约可以连接至链下数据提供商、web API、企业系统、云服务商、物联网设备、支付系统以及其…

高分三号1米分辨率飞机检测识别数据集

二、背景介绍 合成孔径雷达(Synthetic Aperture Radar, SAR) 是一种主动式的微波成像系统&#xff0c;它不受光照、云雾 和气候等自然条件影响&#xff0c;具备全天时、全天候对地 观测的能力&#xff0c;已成为遥感领域重要的信息获取平 台。近年来&#xff0c;随着遥感成像技…

Redis主从复制集群的介绍及搭建

在现代的软件开发中&#xff0c;数据的可靠性和可用性是至关重要的。Redis&#xff0c;作为一个开源的、内存中的数据结构存储系统&#xff0c;以其出色的性能和灵活的数据结构&#xff0c;赢得了开发者们的广泛喜爱。而 Redis 的主从复制功能&#xff0c;更是为我们提供了一种…

基于AHP模型指标权重分析python整理

一 背景介绍 日常会有很多定量分析的场景&#xff0c;然而也会有一些定性分析的场景针对定性分析的场景&#xff0c;预测者只能通过主观判断分析能力来推断事物的性质和发展趋势然而针对个人的直觉和虽然能够有一定的协助判断效果&#xff0c;但是很难量化到指标做后期的复用 …

Mybatis学习笔记2 增删改查及核心配置文件详解

Mybatis学习笔记1 Mybatis入门_biubiubiu0706的博客-CSDN博客 将Mybatis进行封装 SqlSessionUtil工具类 package com.example.util;import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFacto…

modinfo对比内核版本号

加载内核&#xff0c;出现版本不一样 cat /proc/verison查看内核板本 模块版本&#xff1a;显示模块的版本号。 $ modinfo [OPTIONS] [MODULE] 参数说明-F, --field <field>: 指定要显示的字段&#xff0c;可以使用逗号分隔多个字段。-k, --kernel <kernel>: 指定…

第15章_瑞萨MCU零基础入门系列教程之Common I2C总线模块

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

香橙派使用外设驱动库wiringOP来驱动蜂鸣器

硬件接线 回顾香橙派的物理引脚对应&#xff1a; 所以将VCC接到1&#xff0c;GND接到6&#xff0c;I/O口接到7&#xff1a; 代码编写 香橙派的wiringOP库提供了很多的例程&#xff0c;可以将blink.c拷贝进自己的代码文件夹来修改&#xff1a; 小插曲---将手动对齐的Tab和自动对…

《向量数据库指南》——向量数据库和关系型数据库的区别?

向量数据库和关系型数据库是两种不同类型的数据库系统,它们在数据模型、数据存储、查询操作等方面存在许多区别。以下是向量数据库和关系型数据库的主要区别: 1、数据模型: 向量数据库:向量数据库专门设计用于存储和查询向量数据,这些数据通常表示为数值向量或嵌入向量。向…

精品基于NET实现的教育资源配置管理系统

《[含文档PPT源码等]精品基于NET实现的教育资源配置管理系统》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程等 软件开发环境及开发工具&#xff1a; 开发软件&#xff1a;VS 2017 &#xff08;版本2017以上即可&#xff0c;不能低于2017&#xff09…

50个渗透(黑客)常用名词及解释

目录 前言 一.渗透测试 二.网络安全 三.安全攻击 四.黑客工具 五.渗透方法 六.网络钓鱼 七.攻击技术 八.其他名词 总结 前言 网络安全是当今互联网时代不可忽视的重要议题。随着科技的发展&#xff0c;黑客渗透技术也日益复杂和潜在危险。为了加强对网络安全的认识&…

【无标题】C/C++内存管理

目录 一. C/C内存分布 二. C语言中动态内存管理方式 1.malloc/calloc/realloc和free 三. C内存管理方式 1.new/delete操作内置类型 2.new和delete操作自定义类型 四.malloc/free和new/delete的区别 五.内存泄漏 1.什么是内存泄漏&#xff0c;内存泄漏的危害 一. C…

makefile之伪目标PHONEY

当前目录有同makefile中同名的文件,make目标是不会执行的 clean:的依赖是空的,执行的规则条件没有满足. 伪目标是为了解决这个问题,在clean前面增加.PHONEY:clean include Makefile.config SRC : $(wildcard *.c wildcard audio_module/*.c) SRC_OBJ $(patsubst %.c,%.o,$(S…

SpringCloud简介 + Eureka注册发现中心

目录 1.SpringCloud简介 2. Eureka注册发现中心 2.1 Eureka简介 2.2 Eureka的处理机制 2.2.1 Register——服务注册 2.2.2 Renew——服务续约 2.2.3 Eviction——服务剔除 2.2.4 Cancle——服务下线 2.3 Eureka的配置文件 2.4 创建第一个Eureka项目 2.5 Eureka服务注…

C语言指针详解(2)------指针用法(概念+举例)非常详细易理解

C语言指针用法详解及举例 在学习用法之前&#xff0c;大家可以看看我上一节对指针的分类哦&#xff0c;这里我们在复习一下指针的概念&#xff1a; 1.指针就是个变量&#xff0c;用来存放地址&#xff0c;地址唯一标识一块内存空间。 2.指针的大小是固定的4/8个字节&#xff08…

Pytorch入门(6)—— 梯度计算控制

前文 PyTorch入门&#xff08;2&#xff09;—— 自动求梯度 介绍过 Pytorch 中的自动微分机制&#xff0c;这是实现神经网络反向传播的基础&#xff0c;也是所有深度学习框架最重要的基础设施之一梯度计算是需要占用计算资源的&#xff0c;而我们并不总是需要计算梯度&#xf…

锯片检测示例

1.锯片检测 1.1 应用示例目的与思路 (1) 提取并筛选锯齿的轮廓&#xff1b; (2) 对筛选后的锯齿轮廓进行直线拟合&#xff1b; (3) 统计正常锯齿的角度和缺陷锯齿的个数。 1.2 应用示例相关算子介绍 (1) threshold_sub_pix(Image : Border : Threshold : ) 功能&#xf…

应用开发平台集成工作流系列之10——流程建模功能环节业务逻辑处理的设计与实现

背景 基于工作流的表单流转&#xff0c;在某些特定的环节&#xff0c;需要执行一些业务逻辑处理。例如动态分配节点处理人、发送邮件或短信给待办用户、统计流程处理时长判断是否超时&#xff0c;以及业务层面数据处理&#xff08;例如&#xff0c;在请假流程中将部门领导审批…