Compose原理 - 整体架构与主流程
一、整体架构
在官方文档中(Jetpack Compose 架构层 | Android Developers),对Compose的分层有所阐述:
其中
Runtime:提供Compose的基础运行能力,包括State、Side-effects、CompositionLocal、Composition等等相关API。
UI:涉及到绘制的部分,主要是LayoutNode,Modifiler相关。
Foundation:提供Column、Row等“设计系统无关”的组件
Matieria:提供Material Design相关能力,比如主题、Color、Style等。
其中,Runtime的主要包含Composition的Composition, Recomposer, ComposeNode, 和RecomposeScope等主要组件。除此之外,还有State相关的Snapshot的部分。这些是Compose得以实现的核心。本文主要阐述的也就是这部分的架构。
Compose源码架构:
Jatpack Compose是以工具包的形式集成在Android的绘制体系中的。所以其本质上,是嵌入到了DecorView的Content中。换言之,Compose所组织的内容最终绘制在了ComposeView,再将ComposeView添加到DecorView中。如此Compose便集成到了Android的View体系。
在上图中,有几个重要的类:
Composition:可视为Composer的容器,主要负责与外部其他角色进行对接。内部包含了一个Composer。如上图,一般情况下,一个DecorView中包含一个Composition,即一个Activity对应一个Composition。如果使用了TabRow、BoxWithConstraints等组件,其内部会调用SubcomposeLayout,从而会创建多个子Composition实例。
Composer:代表一个Compose树,会内部对树的构建、重组等做一个整体的调度和管理
SlotTable:可以理解为实现Compose树的具体数据结构。其内部主要有两个数组成员:IntArray类型的groups和Array类型的slots。其中groups用来记录一个group的基本信息和其parent group,从而可以构建一个group树。而slots用来存储每个group内的具体数据。而对于这两个数组成员的具体操作,是通过Gap Buffer的方式进行处理的,这是一种在数据插入过程中移动Gap区域,而不是数组元素的方式来达到数据更新的目的。在Gap区域不移动的情况下,仅仅通过几个指针的操作来快速访问数据,由于Compose树的整体结构相对是稳定的,Gap的移动频率相对较少,因此大部分对数组的访问能达到O(1)的时间复杂度。从架构图看到,实际上每个Composition包含了两个SlotTable,一个用于写入,一个用于读取,在写入后通过同步机制将数据同步到用于写入的SlotTable中。读写的隔离有助于频繁写操作的过程中提高性能。
Group:SlotTable内的数据结构,通过内部的parent属性记录其父Group,从而整体上可以构建为一个树形结构。可以简单理解为,一个Composable(一个自定义或内置Composable函数)对应一个Group。不同类型的Composable对应不同的Group类型。因此一个Compose树,实际上就是Group树。
Recomposer:用于触发和管理Compose树的组建和重组。在Recomposer内部有一个while循环,由当前DecorView的viewTreeLifecycle的ON_CREATE时启动,随后如果有compositon被标记需要重组,则会进行重组流程,否则会挂起等待。
Snapshot:快照系统,用于对State值的版本进行管理。Snapshot的思想类似Git等版本管理系统,即,父分支快照基于自身拉出一个子分支快照,子快照只可以看到到自身快照内的State值的变更,对其他分支中相同State的变更不可见。子快照可以通过apply接口将自身变更合入到父分支快照。换言之,父快照的值对子快照是可见的,子快照对父快照是不可见的,一直到子快照apply()。每次重组都会创建一个快照,重组结束后会apply该快照到父快照,同时,快照也是ThreadLocal的,即是线程隔离的。这样,每次重组时所访问的State值的版本都是相互隔离的互不影响,在重组完成后再去merge。
RecomposeScope:Compose数在SlotTable中通过group来表达,但并非所有的group都是可重组的,group有很多类型。为了对可重组的group的范围进行标记圈定,在创建可重组group时会创建对应的RecomposeScope,并保存起来。当state变更导致重组时,会通过当前state对应的scope找到要重组的范围,进行重组。
Applier:SlotTable存储的只是存储了Compose树的结构信息以及树组合过程中涉及到的remember、state等数据信息,而具体的绘制是由LayoutNode树来负责的。Applier就是作为Compose树与LayoutNode之间的桥梁。当初次组件或者后续重组完成之后,会通过Applier通知到LayoutNode。随后LayoutNode根据提供的信息对发生了变化的Composable(保存在changes中)进行绘制。
ChangeList:Composition中changes变量所对应的具体类型。ChangeList存储Composable的具体变化,这些变化会通过Applier最终体现为LayoutNode树的变更。
LayoutNode:Composable在绘制层面的结构体。Compose树最终会转化为LayoutNode树,measure、draw都是在LayoutNode上进行的。
二、Compose运行流程
将Compose运行流程概述为:初始化、Composition(组合/重组并收集变更信息)、Applier(应用变更信息)、LayoutNode测量绘制
1. 初始化
Compose Runtime库中的ComponentActivity,提供了setContent扩展方法,用来支将ComposeView嵌入到Android固有的View体系中。ComposeView首先创建AndroidComposeView,并将AndroidComposeView通过addView加入到Android View树。Compose的设计是跨平台的,AndroidComposeView是针对Android平台的一些具体实现,Compose绘制的结果最终将体现在AndroidComposeView上,而AndroidComposeView的onMeasure和onLayout、dispatchDraw等也最终会传递给Compose体系中的LayoutNode,如此构成了Compose与Android固有View体系的结合。
ComposeView中,同时也会对Recomposer和Composition进行初始化。Recomposer用来重组管理,Composition代表Compose的组合树的容器。
2. Composition(“组合/重组“并变更信息)
2.1 首次组合
在上一步的setConent过程中,会调用Recomposer的composeInitial,开始对Compose树的初始组合,也就是构建Compose树的过程。
composing是Recomposer的重要方法,每次组合/重组的都会调用,可视为真正组合/重组的起点。本节中为了叙述方便,“组合”和“重组”统一表述为重组。在composing开始重组时,会首先通过Snapshot创建一个快照副本,后续本次重组所读写的State,都是在这个快照范围内进行的,不会影响其他快照。当然,其他快照也不会影响本次重组的快照,这样每次重组所使用的数据都是相互隔离的。
创建快照后,会开始调用Composition进行重组流程。每个Composition内有一个Composer类,执行具体重组动作。我们把所有@Composable修饰的自定义函数称作Composable,setContent中的content lambda也是一个Composable。在Composer中通过invokeComposable开始执行content lambda,由此开始一系列的Composable递归调用构建Compose树。我们以其中一个Composable为例,每个Composable执行过程中,会通过startXXXGroup在SlotTable中构建一个Group,这个Group将被保存在SlotTable的groups数组中。Group在不断创建过程中与Composable函数一样保持的对应的嵌入关系,也就是父子关系。后续会将Composable内相应的数据(Group本身数据,函数参数、remember、state值等)保存在这个Group对应的Slots数组内。
在创建完Group后,会通过addRecomposeScope创建一个RecomposeScope,这个RecomposeScope代表了本Group内state变更时所对应的变化范围。随后,这个scope本身也会作为对应Group的数据通过updateValue存到SlotTable的slots数据里。
以上完成后,会将scope通过insertSlots将其保存在changeListWriter中,通过scope可以找到对应的Group及其数据。所以本过程可以理解为将新建/变更的group暂存在changeListWriter中。
到此,通过Composable不断的递归调用,整个Compose树构建完毕,实际上就是StlotTable的Group树构建完毕。并且构建过程中的group信息(全量)本暂存changeListWriter,等待进行具体测量、布局和绘制。
2.2 重组
Compose的特点之一就是响应式编程,数据的变化驱动页面变化,这个过程称为重组。最长见的重组便是Composable内读取的state的值变更时导致的重组。过程如下:
在Recomposer每次重组时创建Snapshot时,会注册该Snapshot的read/wri
te监听。在重组过程中,如果遇到某个Composable读取了某个State,就会把该State存储到Composable对应的RecomposerScope内,并将该scope与state的对应关系存储在observations中。当对State进行write写入时, Recomposer会通过Snapshot的writeObserver监听到写入动作,并且通知Composition,Composition通过遍历observations取出state对应的scope,然后将其加入到invalidations缓存,随后也会将本composition作为invalid加入到Recomposer的compositionInvalidations缓存。
另一方面,Recomposer本身通过WindowRecomposer将自身与onCreate声明周期绑定,在onCreate时会运行runRecomposeAndApplyChanges函数,内部有一个while循环,运行频率会和vsync对齐。平时挂起,只要compositionInvalidations有了变更,就会开始运行。其运行过程为,将compositionInvalidations内的compostion取出, 调用其recompose函数。recompose内会将之前的invalidations内的scope取出,找到其对应的Group,调用Composer的doCompose对其进行变更。
state值的变更可能导致Composable位置移动,删除、插入等,这些都转化SlotTable对为Group的操作。在更新完SlotTable后,将Group的变更信息保存在changeListWriter。
从上面过程可以看出,state的变化并不会同步导致compose树立即更新,而是先存在
invalidations内,等待下一vsync信号时在处理。
3. Applier(应用变更信息)
现在,无论初始组合还是重组,最终的变更信息都会保存到changeListWriter。我们来看changeListWriter如何最终被转变为具体的页面变更。
在重组过程中每一个对Group的操作,最后会转变成一条指令通知给changeListWriter,比如新建一个Group,会调用changeListWriter的insertSlots函数,insertSlots会向ChangeList去push一条指令。这条指令保存在ChangeList的options中。Recomposer的runRecomposeAndApplyChanges在后续会调用composition.applyChanges,随后取出options的指令,进行执行。
在具体执行时,每种指令都具体转化为Options类的一个具体子类。例如创建一个Node Group的insertSlots,会由InsertNodeFixup来执行,随后通知作为root的LayoutNode,进行创建group对应的LayoutNode并加入其LayoutNode树的对应位置。
每种对Group的新建、移动、删除等操作,最后都变成了对响应layoutNode的新建、移动、删除操作。
4. LayoutNode测量、布局和绘制
LayoutNode结构变更完了,随后需要将其绘制出来。整个过程也主要分为Measure、Layout和Draw。MeasureAndLayoutDelegate是LayoutNode的测量代理类,执行其测量的具体方法。每次LayoutNode树变更时,会将变更的layoutNode存储在MeasureAndLayoutDelegate的relayoutNodes里。然后会通过传统的方式通知AndroidComposeView invalid,此时onMeasure会被回调。随后onMeasure会调用measureAndLayout取出MeasureAndLayoutDelegate的relayoutNodes缓存,然后进行从下到上的重新测量工作。Layout的过程也类似。
待测量布局完毕,AndroidComposeView的dispatchDraw被系统调用,此时,通过构建或更新LayoutNode对应的layer,并将其绘制在封装了Canvas的AndroidCanvas上。从而完成了绘制工作。
此处可见,绘制过程只对有变更的LayoutNode进行绘制,由此之前的重组的差量变更过程才有意义。
至此,Compose从初始化、重组到最终绘制的流程大致描述完毕。
相关文章:

Compose原理 - 整体架构与主流程
一、整体架构 在官方文档中(Jetpack Compose 架构层 | Android Developers),对Compose的分层有所阐述: 其中 Runtime:提供Compose的基础运行能力,包括State、Side-effects、CompositionLocal、Compositio…...
从0开始学vue:实现一个简单页面
Vue.js 是一个渐进式JavaScript框架,用于构建用户界面。下面我将带你从零开始学习Vue.js并创建一个简单的可运行页面。 1. 准备工作 首先,你需要了解几种学习Vue.js的方式: 方式一:使用CDN引入(最简单的方式&#x…...
在机器视觉测量和机器视觉定位中,棋盘格标定如何影响精度
棋盘格标定是机器视觉(尤其是基于相机的系统)中进行相机内参(焦距、主点、畸变系数)和外参(相机相对于世界坐标系的位置和姿态)标定的经典且广泛应用的方法。它的质量直接、显著且多方面地影响最终的视觉测量和定位精度。 以下是棋盘格标定如何影响精度的详细分析: 标定…...

CppCon 2014 学习: C++ Test-driven Development
“Elephant in the Room”这个比喻常用来形容那些大家都知道但没人愿意讨论的重大问题。 这段内容讲的是软件质量管理的经典做法和潜在的问题: 经典做法:开发完成后才进行人工测试(manual testing after creation)。隐喻“Cape o…...

RAGflow详解及实战指南
目录 前言 一、RAGflow核心技术解析 1. 技术原理:检索与生成的协同进化 2. 架构设计:分层模块化与高扩展性 3. 核心优势:精准、高效、安全 二、RAGflow实战应用场景 1. 企业知识库搭建 2. 智能客服系统 3. 投资分析报告生成 4. 制造…...
JWT 不对外,Session ID 对外:构建安全可控的微服务认证架构
以下是一篇围绕“JWT不对外,Session ID对外”的专业架构设计文章,适用于技术团队评审、技术博客发布或系统设计文档引用: JWT 不对外,Session ID 对外:构建安全可控的微服务认证架构 在构建分布式微服务系统时&#x…...

[Godot] 如何导出安卓 APK 并在手机上调试
在之前的文章中,我们已经详细介绍了如何配置 Godot 的安卓应用开发环境,包括安装 Android SDK、配置 Java 环境、设置 Godot 的 Android 导出模板等。本篇文章将进一步讲解如何将 Godot 项目导出为安卓 APK 文件,并实现在手机上进行调试运行。…...
React 路由管理与动态路由配置实战
React 路由管理与动态路由配置实战 前言 在现代单页应用(SPA)开发中,路由管理已经成为前端架构的核心部分。随着React应用规模的扩大,静态路由配置往往难以满足复杂业务场景的需求,尤其是当应用需要处理权限控制、动态菜单和按需加载等高级…...
ZYNQ sdk lwip配置UDP组播收发数据
🚀 一、颠覆认知:组播 vs 单播 vs 广播 通信方式目标设备网络负载典型应用场景单播1对1O(n)SSH远程登录广播1对全网O(1)ARP地址解析组播1对N组O(1)视频会议/物联网群控创新价值:在智能工厂中,ZYNQ通过组播同时控制100台AGV小车,比传统单播方案降低92%网络流量! 🔧 二、…...
11.21 LangGraph多轮对话系统实战:三步构建高效信息整理引擎,效率提升300%!
关键词:LangGraph 工作流设计, 信息整理助理, 多轮对话系统, 状态管理, 条件分支控制 信息整理助理工作流设计 信息整理助理需要完成 多源数据收集 → 信息分类 → 深度分析 → 结构化输出 的完整流程。通过 LangGraph 的图结构工作流,可实现复杂逻辑的模块化编排: #mermai…...
高光谱成像相机:基于高光谱成像技术的玉米种子纯度检测研究
种子纯度是衡量种子质量的核心指标之一,直接影响农作物产量与品质。传统检测方法(如形态学观察、生化分析)存在耗时长、破坏样本、依赖人工等缺陷。近年来,高光谱成像技术因其融合光谱与图像信息的优势,成为无损检测领…...

Linux《文件系统》
在之前的系统IO当中已经了解了“内存”级别的文件操作,了解了文件描述符、重定向、缓冲区等概念,在了解了这些的知识之后还封装出了我们自己的libc库。接下来在本篇当中将会将视角从内存转向磁盘,研究文件在内存当中是如何进行存储的…...

NLP学习路线图(十六):N-gram模型
一、为何需要语言模型?概率视角下的语言本质 自然语言处理的核心挑战在于让机器“理解”人类语言。这种理解的一个关键方面是处理语言的歧义性、创造性和结构性。语言模型(Language Model, LM)为此提供了一种强大的数学框架:它赋…...
【Python办公】将Excel表格转json(字典)数据-可自定义key和value
目录 专栏导读背景介绍库的安装数据源准备代码1:key1列,value所有列代码1:key多列,value所有列代码3:key自选,value自选总结专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关…...
Java内存区域与内存溢出异常分析与解决
在 Java 开发中,内存管理和内存溢出异常( OutOfMemoryError)是一个至关重要的主题。Java 虚拟机(JVM)的内存区域分为多个部分,每个区域都有其特定的用途和限制。当这些区域的内存耗尽时,就会触发…...

Python训练第四十天
DAY 40 训练和测试的规范写法 知识点回顾: 彩色和灰度图片测试和训练的规范写法:封装在函数中展平操作:除第一个维度batchsize外全部展平dropout操作:训练阶段随机丢弃神经元,测试阶段eval模式关闭dropout 昨天我们介绍…...
硬件实时时钟(RTC)
硬件实时时钟(RTC)详解 硬件实时时钟(Real-Time Clock,RTC)是计算机主板上的一个独立计时芯片,用于在系统关机后持续记录时间。它不依赖操作系统,由纽扣电池(如CR2032)供…...

InternVL2.5-多模态大模型评估专业图片
具备图像理解功能的大模型InternVL2.5,能有效解析大部分图片。 对于专业图片如医学细胞切片,从专业角度解析,能推动模型应用到更广泛的领域。 InternVL2.5解析示例 prompt(胸部癌变细胞图片,来自PanNuke) 请评估这个组织的风险 InternVL2.…...

医疗数理范式化:从范式迁移到认知革命的深度解析
引言 在当代医疗领域,数理思维已经从辅助工具逐渐发展成为核心决策支持系统的关键组成部分。随着数字技术的迅猛发展,医疗行业正经历着前所未有的变革,而数理思维作为这一变革的核心驱动力,正在深刻重塑医疗实践的方方面面。数理思维在医疗领域的应用,本质上是将抽象的数…...

图神经网络在信息检索重排序中的应用:原理、架构与Python代码解析
现代信息检索系统和搜索引擎普遍采用两阶段检索架构,在人工智能应用中也被称为检索增强生成(Retrieval-Augmented Generation, RAG)。在初始检索阶段,系统采用高效的检索方法,包括词汇检索算法(如BM25&…...
leetcode hot100 二叉树(一)
1.二叉树的中序遍历 中序遍历(中根遍历):左-根-右顺序,递归实现。注意设置递归终止条件。 class Solution { public:void search(TreeNode* root,vector<int>& ans){if(!root) return ;search(root->left,ans);ans.…...
【技术支持】安卓11开机启动设置
<!-- 开机自启动权限 --><uses-permission android:name"android.permission.RECEIVE_BOOT_COMPLETED" /><!-- 自启动权限 --><uses-permission android:name"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /><!-…...

现代数据湖架构全景解析:存储、表格式、计算引擎与元数据服务的协同生态
本文全面剖析现代数据湖架构的核心组件,深入探讨对象存储(OSS/S3)、表格式(Iceberg/Hudi/Delta Lake)、计算引擎(Spark/Flink/Presto)及元数据服务(HMS/Amoro)的协作关系,并提供企业级选型指南。 一、数据湖架构演进与核心价值 数据湖架构演进历程 现代数据湖核心价…...

全志F1c200开发笔记——移植Debian文件系统
1.搭建环境 sudo apt install qemu-user-static -y sudo apt install debootstrap -y mkdir rootfs 2.拉取文件系统 这边我参照墨云大神的文档,但是华为镜像已经没有armel了,我找到了官方仓库,还是有的,拉取速度比较慢 sudo d…...
dis css port brief 命令详细解释
华为交换机命令 display css port brief 详细解释 display css port brief 是华为交换机中用于 快速查看堆叠(CSS,Cluster Switch System)端口状态及关键参数 的命令,适用于日常运维、堆叠链路健康检查及故障定位。以下是该命令的…...

支持功能安全ASIL-B的矩阵管理芯片IS32LT3365,助力ADB大灯系统轻松实现功能安全等级
随着自动驾驶技术的快速发展,汽车前灯智能化也越来越高。自适应远光灯 (ADB) 作为一种智能照明系统,在提升驾驶安全性和舒适性方面发挥着重要作用。ADB 系统通过摄像头和传感器获取前方道路信息,例如来车的位置、距离和速度,并根据…...

BFS入门刷题
目录 P1746 离开中山路 P1443 马的遍历 P1747 好奇怪的游戏 P2385 [USACO07FEB] Bronze Lilypad Pond B P1746 离开中山路 #include <iostream> #include <queue> #include <cstring> using namespace std; int n; int startx, starty; int endx, endy; …...

UE5 编辑器工具蓝图
文章目录 简述使用方法样例自动生成Actor,并根据模型的包围盒设置Actor的大小批量修改场景中Actor的属性,设置Actor的名字,设置Actor到指定的文件夹 简述 使用编辑器工具好处是可以在非运行时可以对资源或场景做一些操作,例如自动…...
手写multi-head Self-Attention,各个算子详细注释版
文章目录 MultiHeadAttentionFormal的实现操作详解1. 🔍 attention_mask2. 🔍 matmul✅ 其他实现方式1. 使用 运算符(推荐简洁写法)2. 使用 torch.einsum()(爱因斯坦求和约定)3. 使用 torch.bmm()…...
基于 Three.js 的文本粒子解体效果技术原理剖析
文章目录 一、整体架构与核心库引入二、Three.js 场景初始化三、文本粒子数据创建五、动画与交互实现在前端开发领域,通过代码实现炫酷的视觉效果总能给用户带来独特的体验。本文将深入剖析一段基于 Three.js 的代码,解读其实现文本粒子解体效果的技术原理。 实现效果: 一、…...