Jetpack Compose -> 状态机制的背后秘密

解密Jetpack Compose重组机制:SlotTable与高效渲染的奥秘 本文深入解析Jetpack Compose的重组机制,重点探讨SlotTable数据结构如何实现高效渲染。通过分析Positional Memoization、Gap Buffer优化技术及Diff算法,揭示Compose在精准更新和状态保持方面的核心技术,并提供实战中的性能优化技巧,帮助开发者提升UI渲染效率。 阅读详情

前言


上一章我们讲解了 Jetpack Compose 的无状态、状态提升、单向数据流 本章我们讲解下状态机制的背后秘密

List


前面我们讲过,通过 by mustableStateOf() 就可以被 Compose 自动订阅了;我们前面是通过 String 类型进行的自动订阅,那么换成其他类型是可以的吗?答案是可以的,只要被 mustableStateOf 包裹之后,它就会被一个 MustableState 包裹,这个 MustableState 就是一个代理对象,状态的订阅和更新会被代理到它上面,所以 我们使用其他类型也是可以的,我们可以来看一个例子:

private var number by mutableStateOf(1)

    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Android_VMTheme {
                Surface {
                    Text(text = "当前数值是: $number", modifier = Modifier.clickable {
                        number ++
                    })
                }
            }
        }
    }

我们执行 Text 的点击事件,让 number++ 来看下 number 的变化是不是可以及时的更新到结果,运行看下:

SVID_20240408_122244_1.gif

可以看到,是可以实时的更新的,所以说,换成其他类型,也是可以的;

这个时候,可能会有人有疑问了,那么换成非基本数据类型可以吗?比如换成 List,好,我们来试一下

private var nums by mutableStateOf(mutableListOf(1, 2, 3))

@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        Android_VMTheme {
            Surface {
                Column {
                    for (num in nums) {
                        Text(text = "当前是第 $num 的文字")
                    }
                }
            }
        }
    }
}

我们来运行看下是否是我们想要的效果:

image.png

达到了我们期望的效果,但是,接下来,我们对这个 List 进行一下修改看下界面是否还会跟着改变,怎么修改呢?我们可以继续使用点击监听的逻辑;

private var nums by mutableStateOf(mutableListOf(1, 2, 3))

    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Android_VMTheme {
                Surface {
                    Button(onClick = {
                        nums.add(nums.last() + 1)
                    }) {
                        Text(text = "添加内容")
                    }
                    Column {
                        for (num in nums) {
                            Text(text = "当前是第 $num 的文字")
                        }
                    }
                }
            }
        }
    }

我们额外增加了一个 Button,点击的时候,每点击一次,就给 List 增加一项内容,每次取最后一个值进行加1的操作,我们来运行看下效果:

SVID_20240408_123643_1.gif

可以看到,并没有达到我们期望的效果,界面内容并没有随着 List 内容的改变而改变,那么这又是为什么呢?我们来一探究竟;

我们先来想一下,这个被 by mustableStateOf 初始化的对象为什么可以被监听?因为它的get 和 set 函数被加了钩子,它的赋值和取值操作被代理了,所以它能够被监听,也就是 nums 的赋值取值被 mustableStateOf 代理了,所以它能够被监听。这个 nums 读的地方在 for 循环中被读取,那么『写』的地方是在哪里呢?是在 Button 的点击监听中更新了,这种写法看起来是没有问题的呀?那么它到底是哪里不对呢?

其实,就是在 nums 更新的地方不对!

nums 的 set 被加了钩子,是针对的 nums 的 set 方法,而不是 add 方法,所以这个改动是不生效的!也就是说 add 逻辑不会触发 setValue 的调用,所以这个改动不生效,也就不会触发自动更新的操作了;也就是说 如果我们强行增加一个 ReCompose 的过程,它的结果是会更新的;

我们来看一个 ReCompose 的过程:

private var number by mutableStateOf(1)
private var nums by mutableStateOf(mutableListOf(1, 2, 3))

@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        Android_VMTheme {
            Surface {
                Column {
                    Text(text = "当前数值是: $number", modifier = Modifier.clickable {
                        number ++
                    })
                    Button(onClick = {
                        nums.add(nums.last() + 1)
                    }) {
                        Text(text = "添加内容")
                    }
                    Column {
                        for (num in nums) {
                            Text(text = "当前是第 $num 的文字")
                        }
                    }
                }
            }
        }
    }
}

我们在按钮的上面增加了一个 文字,给这个文字增加了点击监听,同时更改这个 number 的值,因为这些整体是被一个 Column 包裹,那么当 number 改变的时候,整个的区域会被 ReCompose,我们运行看下效果:

SVID_20240408_125301_1.gif

可以看到,当 number 改变的时候,List 的更新也呈现了出来;

所以,Compose 的监听更新是对 『赋值』操作的监听更新,像这种『nums.add(nums.last() + 1)』修改内部状态的是不会触发更新的,从而不会触发界面的刷新;

重新赋值

问题定位了,那么我们怎么来实现界面的刷新呢?首先大家想到的肯定是重新赋值,怎么赋值呢?

Button(onClick = {
    nums = nums.toMutableList().apply {
        add(nums.last() + 1)
    }
}) {
    Text(text = "添加内容")
}

通过 nums.toMutableList() 转换成一个新的 list 之后赋值给 nums,这样就是执行了一个『赋值』操作,我们运行看下效果:

SVID_20240408_130329_1.gif

我们通过点击,直接实现了界面的刷新操作~

但是,写到这里的时候,好多人会提出疑问了,这种写法会不会带来性能问题,以及这种写法是不是太笨重了,有没有更优雅的写法呢?答案是,不会带来性能问题,有的~

mustableStateListOf

Compose 针对 List 给我们提供另一个 API,叫作 mustableStateListOf,它内部也会创建一个 MustableStateList,并且它内部的变化也会被 Compose 观测到;

    private var nums = mutableStateListOf(1, 2, 3)

    @OptIn(ExperimentalMaterial3Api::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Android_VMTheme {
                Surface {
                    Column {
                        Text(text = "当前数值是: $number", modifier = Modifier.clickable {
                            number ++
                        })
                        Button(onClick = {
                            nums.add(nums.last() + 1)
                        }) {
                            Text(text = "添加内容")
                        }
                        Column {
                            for (num in nums) {
                                Text(text = "当前是第 $num 的文字")
                            }
                        }
                    }
                }
            }
        }
    }

我们直接使用 mutableStateListOf 来监听内部的变化,我们运行看下效果;

SVID_20240408_131312_1.gif

完美的实现了状态变化的界面刷新,并且比起前一种写法要好了很多;

mustableStateMapOf

跟 mutableStateListOf 比较相似的是 mustableStateMapOf 它是创建的一个 Map,并且监听这个 Map 的内部变化;

总结


Compose 里面用 mustableStateOf 创造出的 MustableState 是很简单的判断『是否重新赋值』 所以其无法监听普通的 List 和 Map,包括普通的 mustableListOf 和 mustableMapOf, 只能使用 mutableStateListOf 和 mustableStateMapOf 来解决;

好了,Compose 的课程今天就讲到这里吧~~

下一章预告


重组的性能风险和优化

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~~

Jetpack Compose原理与性能优化:重组、快照与渲染链路全解析 在Android UI开发中,声明式UI框架正逐渐取代传统的View体系,而Jetpack Compose作为其中的代表,其核心价值在于通过状态驱动界面自动更新,大幅简化了UI开发流程。要真正发挥其性能优势,必须理解底层原理:Composable函数经编译器改写后依赖Composer与槽位表记录UI结构,快照系统负责追踪状态变更,重组机制按帧调度需要更新的Scope,最终通过LayoutNode与RenderNode完成渲染。这一链条决定了状态读取粒度、类型稳定性与lambda捕获都会直接影响界面流畅度。在 阅读详情

相关推荐

Compose鱼骨加载动画:从原理到实践的全方位解析

本文深入解析了在Jetpack Compose中实现鱼骨加载动画(骨架屏)的原理与实战。通过剖析Shimmer动画的核心机制,并结合compose-shimmer库,详细演示了如何从零构建一个能有效降低用户等待焦虑、提升App感知流畅度的加载效果。文章涵盖了从状态管理、UI构建到性能优化与自定义的全流程,为安卓开发者提供了高效的应用方案。

weixin_28323057的博客 87

compose监听列表数据

才可以对列表的数据进行监听。

github_35581409的博客 389

Android Compose 排查指南:解决 Composable 渲染问题

在使用 Jetpack Compose 时,布局的排列和大小控制是关键。通过合理地使用Modifier和理解 Compose 的布局规则,我们可以避免很多常见的渲染问题。记住,这样的修饰符可能会在某些情况下导致意外的布局行为,所以需要根据具体情况进行调整。通过这个案例,我们不仅解决了特定的问题,也学习到了如何更有效地使用 Compose 来构建灵活且美观的界面。

m0_62153576的博客 177

Compose:State 状态订阅更新原理

compose

Vincent的博客 1349

Jetpack Compose实战指南:社区驱动的示例与教程集合

回顾这几年的 Android 开发演进,从 MVC 到 MVP 再到 MVVM,我们一直在追求更好的可维护性与可测试性。而 Jetpack Compose 的出现,终于让我们离“理想中的 UI 开发”又近了一步。它带来的不仅是语法上的简化,更是认知负担的降低。你现在可以:用 Kotlin 写 UI,享受完整的 IDE 支持 🛠️状态与 UI 自动同步,告别手动刷新 🔄组件高度可复用,团队协作更高效 👥一套代码多平台运行,开发效率翻倍 💻📱。

weixin_34725745的博客 447

VFIO框架源码分析(二十一)- IOMMUFD 模式下的 DMA 映射机制

在虚拟化 I/O 的世界里,无论上层管理面(如对象模型、设备绑定)如何重构,数据面最核心的任务始终未变——DMA 映射(DMA Mapping)。即如何建立 Guest Physical Address (GPA) 到 Host Physical Address (HPA) 的映射关系,并将其填入 IOMMU 的硬件页表中,使得直通设备能够正确访问虚拟机内存。在 Legacy VFIO (Type1) 时代,这一职责由承担,它通过ioctl 操作 Container。

qq_36071928的博客 1007

Android Compose 十:常用组件列表 监听

那么我们之前使用recyclerview的时候,可以通过LayoutManager监听滑动,并且可以获取到当前显示的第一条条目和最后一条条目;这里暂时不知如何判断 条目完全显示的情况,LayoutManager是有这个方法的;大意就是,我们需要监听滑动的状态,用于不同的处理。当未滑动到顶部或者底部时 回调it值一直为0。惯性滑动到顶部时 值 大于0 后 递减。当我们滑动开始和停止的时候打印如下日志。惯性滑动到顶部时 值 小于0 递增。滑动到顶部继续拖拽时 值 大于0。滑动到底部时 值 小于0。

weixin_41648633的博客 1116

STM32实战篇:使用USART发送/接收数据

在发送字符时也可以直接使用其对应的ASCLL码,例如: USART_SendData(USART1, 0x61);

qq_74114817的博客 596

Compose 状态管理

使用 MutableState remember StatelessComposable & StatefulComposable 状态提升 rememberSaveable 支持parceable 不支持parceable 使用ViewModel ViewModelProvider.Factory 使用Flow

xiangxiongfly 890

点餐系统数据库设计--SQL Server

友情链接 1、学生成绩管理系统数据库设计–MySQL 学生成绩管理系统数据库设计–MySQL 2、医疗信息管理系统数据库–MySQL 医疗信息管理系统数据库–MySQL 3、邮件管理数据库设计–MySQL 邮件管理数据库设计–MySQL 4、SQL Server医疗信息管理系统数据库【英文版-源码】–(Medical Management System Database) SQL Server医疗信息管理系统数据库【英文版-源码】–(Medical Management System Database) 5

若如初见 8万+

避坑指南:Unity与Visual Studio开发环境配置中的5个常见问题及解决方案

本文针对Unity与Visual Studio开发环境配置中的5个常见问题,提供了详细的解决方案。重点解决了项目文件生成失败导致命名空间无法识别、调试器无法附加、包管理器与扩展安装混乱、脚本编译错误以及多版本环境冲突等核心痛点,帮助开发者构建稳定高效的开发调试环境。

weixin_27791839的博客 280

Jetpack Compose + Kotlin构建现代App,性能提升300%的秘密

掌握Jetpack Compose与Kotlin开发app的高效组合,提升界面性能300%。适用于现代Android应用开发,声明式UI与简洁代码大幅提升开发效率。构建响应式、高性能App的实用指南,值得收藏。

QuickCode的博客 1007

各种逻辑漏洞

注册:短信轰炸验证码安全问题密码爆破邮箱轰炸用户任意注册、批量注册用户名枚举XSS(有框的地方就可以尝试插XSS)登录:短信轰炸、验证码安全问题、密码爆破、邮箱轰炸SQL注入撞库抓包把password字段修改为空值发送认证凭证替换、比如返回的数据包中包含账号,修改账号就能登录到其他账号Cookie仿冒修改返回包的相关数据,可能会登陆到其他的用户找回密码:短信邮箱轰炸、短信邮箱劫持重置任意用户账户密码、验证码手机用户未统一验证直接跳过验证步骤。

shirlly_的博客 2093

为什么顶级应用都在用compose-rules?Jetpack Compose最佳实践详解

compose-rules是一套针对Jetpack Compose的Lint规则集合,旨在通过自动化检测帮助开发者遵循最佳实践,构建更健康、更高效的Compose应用。作为Twitter Compose规则的积极维护分支,它提供了全面的代码检查能力,已成为众多顶级Android应用确保UI质量的秘密武器。 [![Compose Rules Logo](https://raw.gitcode.co

gitblog_00551的博客 936

GB 44497-2024《智能网联汽车 自动驾驶数据记录系统》标准解读

GB 44497-2024《智能网联汽车 自动驾驶数据记录系统》针对智能网联汽车的自动驾驶数据记录系统进行了全面规范。标准要求我们记录车辆在自动驾驶模式下的所有关键操作数据,如:1. 车辆状态数据,如速度、加速度、转向角度、制动状态等;2. 环境感知数据,如周围物体的位置、速度、类型等;3. 驾驶决策数据,如路径规划、避障决策、紧急制动指令等;4. 系统状态数据,如软件版本、硬件状态、系统警告信息等。在数据存储方面,标准强调了数据的安全性和可靠性。

daopuyun的博客 2669

华为OD机试双机位C卷:采购订单 (Py/Java/C/C++/Js/Go)

华为OD机试双机位C卷 采购订单,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,并提供在线OJ模拟做题。华为OD机试真题【采购订单】

qq_45776114的博客 3534

Jetpack Compose 深入探索系列三:Compose runtime

slot table(插槽表)是一个优化的内存结构,runtime 使用它来存储组合的当前状态。它在初始组合时填充数据,并在每次重新组合时更新。我们可以把它看作是所有Composable函数调用的跟踪,包括它们在源码中的位置、参数、记住的值、CompositionLocals等等。它包含了在合成过程中发生的一切内容。所有这些信息将被 Composer 稍后用于生成下一个更改列表,因为对树的任何更改总是依赖于当前状态。 slot table 记录Composable的状态,而 change list 则是对

川峰的博客 1310

Windows 11 安装 OpenClaw(龙虾):不用WSL、不用虚拟机、启动、使用、停止、卸载

本文详解 Windows 11 原生安装 OpenClaw(龙虾)全流程,无需 WSL 或虚拟机。涵盖环境配置、安全警示、模型接入(如通义千问)及技能初始化。提供启动、文件自动化操作、停止与卸载的完整指南,并附常见认证过期解决方案。助您快速部署本地 AI 智能体,实现电脑操作自动化,提升工作效率。

寒水馨 · 轻量级技术文档 · hanshuixin.org 899

Jetpack Compose 深度剖析:从 UI 声明式编程到渲染内核优化

它不会像传统 View 那样层层嵌套测量布局与绘制流程,而是采用测量(MeasurePass)-> 布局(LayoutPass)-> 绘制(DrawPass)的管线逻辑,通过 LayoutNode 驱动 Compose UI 树的变化。Jetpack Compose 和 HarmonyOS ArkUI 均采用声明式 UI 编程范式,面向多设备场景的响应式 UI 构建,二者在理念相通的同时,在架构设计、状态模型、渲染机制等方面有显著区别。Compose 则采用声明式模型:界面即状态的函数表达。

SOHU_TECH的博客 1343

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

深入解析Jetpack Compose渲染管线:从状态驱动到像素渲染的完整流程

在现代声明式UI框架中,渲染管线是实现高效界面更新的核心技术架构。其核心原理是通过状态管理驱动UI树更新,采用差分算法智能复用组件,最终通过硬件加速渲染到屏幕。这一技术价值在于将开发者从繁琐的命令式UI操作中解放出来,专注于数据与状态的描述,同时通过三个阶段(组合、布局、绘制)的精细控制实现高性能渲染。在移动应用开发、跨平台界面构建等场景中,这种架构显著提升了开发效率和运行时性能。本文以Jetpack Compose为例,深入剖析其渲染管线如何通过状态追踪与重组机制,结合Slot Table数据结构和硬件加

weixin_30915275的博客 420

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

动画背后的秘密:material-motion-compose 进度阈值与缓动曲线原理解析

material-motion-compose 是一款为 Jetpack Compose 与 Compose Multiplatform 实现 Material Motion 动效规范的动画库。很多开发者用它能"一键"获得共享轴(Shared Axis)、淡入淡出(Fade Through)等官方质感动画,却很少深究其原理。本文就带大家拆解它背后两个核心机制——**进度阈值(Progress Th

gitblog_00038的博客 478

Android 技术文章合集 | Jetpack、Kotlin、Window & View、事件机制、多线程、存储等

代码说」:一个专注于 Android 技术细节拆解的分享号,欢迎关注!

小马快跑的博客 931

告别SQL地狱:Jetpack Compose+LitePal打造丝滑数据持久化体验

在Android开发中,数据持久化往往是开发者绕不开的痛点。传统SQLite操作繁琐且容易出错,而LitePal作为一款轻量级ORM框架,正以其简洁的API和高效的性能改变这一现状。本文将带你探索如何通过LitePal与Jetpack Compose的完美结合,实现从复杂SQL操作到优雅数据管理的蜕变,让你的应用开发效率提升300%! [![LitePal框架logo](https://raw.

gitblog_00359的博客 1140

Jetpack Compose -> 重组的性能风险和优化

Jetpack Compose 又一弹来袭:重组的性能风险和优化,带你玩转 Jetpack Compose

IT_Android的博客 1110

Compose 中 collectAsStateWithLifecycle 与重组的核心关系解析

collectAsStateWithLifecycle是Compose中实现状态管理与UI更新的关键桥梁。它通过监听ViewModel数据变化并转换为State对象,触发Compose重组机制更新UI。其核心特点包括:1) 具有生命周期感知能力,仅在START状态时激活监听;2) 通过属性委托自动建立状态依赖关系;3) 数据变化时自动标记相关组件为"脏"状态,驱动重组。与重组机制形成"数据变化触发重组"的因果关系,确保UI始终展示最新数据。该设计既保证了数据一致性,又

琦琦的博客 228

为老设备买份保险:用SSHRD_Script导出板载SHSH Blob的原理与完整操作

如果你手里还有一台 iPhone 5s、6/6s、7/8 或初代 iPad Pro 这类旧设备,**SSHRD_Script** 可以帮你在它们真正淘汰之前买一份"保险":它是一个 Shell 脚本,能在 checkm8 设备(A7–A11 芯片)上创建并启动一个 **SSH Ramdisk**,从而直接读取设备闪存,把**板载 SHSH Blob**(`dumped.shsh`)完整导出保存。这

gitblog_01090的博客 233
上一篇: 如何应对Android面试官 -> Android 系统启动流程浅析
下一篇: 如何应对Android面试官 -> Android 如何实现增量更新,Tinker patch包生成核心原理
老A说
博客等级 码龄14年 1181粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值