Compose:警惕Loop(遍历),图文并茂带你深度释疑,解决的不仅是性能问题

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Compose:警惕Loop(遍历),图文并茂带你深度释疑,解决的不仅是性能问题

1.什么是Loop陷阱?


在Compose的开发过程中,经常遇到会使用到循环的地方,但是简单的循环背后竟然隐含着巨大的性能隐患甚至是奇怪的UI问题,下面看看这个案例:

image.png

UI非常简单,就是一个初始5行的列表,点击按钮后在头部插入一个元素,由于LaunchedEffect的特性,我们可以监听某个重组作用域是否生成了,如果你不懂LaunchedEffect,可以看看笔者的这篇文章:

好的回归正题,启动App之后,得到如下日志:

image.png

没什么问题,App刚进来的时候,Compose检测出列表有5项元素,于是生成了5个重组作用域,于是`LaunchedEffect`也有5个、日志打印了5次。

点击一下按钮,让列表的头部插入一个元素,那么会打印什么样的日志呢?请读者自己先思考5秒:

5秒...

4秒...

3秒...

2秒...

1秒...

揭露答案:

image.png

答案是最后一个元素重新被打印了,哎哟我去,Compose玩花的是吧,插入的是第0个,倒是最后一个给我打印了,发生什么事了

image.png

我埋个小伏笔,抛开这个问题不谈,我们使用布局查看器观察一下重组次数:

image.png

又发生什么事了,我们观察到,插入新元素之后,新增了一个组件,除去他是新增的没有计算到重组次数以外,其余的Text都重组了一次,发生什么事了?更让人疑惑的是,不是插入在开头的元素吗,为什么是最后一个是新增的?

先别急,下面正式进入解惑部分。


2.Loop陷阱的本质:萝卜和坑没绑定


程序员在Compose里面调用forEach的时候,实际上是做了两件事:1.生成N个坑位 2.把数据依次插入坑位。为了降低读者的理解难度,下面使用几张图来解释:

2.1.根据List的长度生成N个坑位

image.png什么是坑位?这是笔者为了方便读者理解生造的词,一个萝卜一个坑嘛,其实这里的坑位就是重组作用域,因为列表有5个元素,因此生成了5个重组作用域,这就是LaunchedEffect被调用了5次的原因

2.2.把数据依次插入坑位

这个没什么好说的,数据一一填入了自己的坑,一个萝卜一个坑。

image.png

2.3.插入新数据,坑爹的地方在此

回想一下我们刚才的步骤,Compose挖了N个坑,然后依次埋萝卜,这有什么问题?这会导致坑位和萝卜不是一一对应的,实际如下图:

image.png好家伙,假如你在地里种了5个萝卜,这时候要在田的开头多种一个萝卜,Compose做了以下的事:

把新的萝卜埋在第一个坑,其余的每一个萝卜都挖起来埋到下一个坑去,最后一个萝卜埋新挖的坑

因此,新挖了一个坑(导致LaunchEffect新调用了一次,而且打印的是最后一个数据),然后全部坑都种了新的萝卜(原来5个坑位发生了重组),这就是开头展示的两个怪现象的本质原因。

image.png

那么有没有一种办法,让每个萝卜在种下去之后就不要离开他自己的坑位了,新加入的萝卜才需要重新挖一个坑呢?有的,请往下看。


3.一个萝卜一个坑,新来萝卜别换坑


如何让每个坑位都能认出他的萝卜呢,答案是使用key这个api,简单把代码改造成如下:

image.png

这里使用key包裹住刚才的代码,key里面的参数使用每个Item的主键,这样每个坑位就和他的萝卜绑定了,不会再做那种把萝卜1搬到坑2的情况,而是坑1和萝卜1对应起来,为了验证正确,我们点一下按钮后,查看一下日志:

image.png

同样是输出了一个日志,但是这个日志对应的是新插入的内容,说明新增的坑位和新增的萝卜是一一对应的,这次不是在最后一个位置新增了坑位,而是在开头新增了坑位,让我们再看看重组的情况:

image.png

可以看到,原来的5个Text(即后面5个Text)虽然也进入了重组阶段,但是由于每一个坑位的萝卜没有发生变化,他们都因为智能重组的机制跳过了重组阶段,而第一个Text(即新插入的Text)由于是新建的,没有发生重组,一切都符合我们的预期了,如果你还不懂,看看下面这张图:

image.png

image.png

简单处理后就比较符合我们的自然逻辑了,Compose默认的行为真的有点反直觉,但是想想也有道理,声明式布局都是自动绑定UI和数据的,如果你不主动声明每一个数据的主键是什么,Compose又如何知道变化后的列表中,哪些数据是新增的,哪些数据是发生了变化的,哪些数据是删除掉了呢,他只能当做整个列表都是变化过的,一视同仁给你全部重组了,这有点类似RecyclerViewnotifyDatasetChanged,我们享受了声明式布局的组件与数据对象自动绑定的便利,同样要付出相应的心智成本。


4.多谈一点,但不多谈


在Comopse的一些懒加载的组件中,例如常见的LazyColumnLazyRowitems中,同样存在key这个属性,道理都是一样的,就是给告诉Compose如何识别一个数据源,让列表发生后,自动找出变化了的数据,而不是对列表进行整体的重组,提高效率,具体不展开说了,因为官方文档讲的很详细,需要的朋友可以自行阅读官方文档


总结

Compose的踩坑之路任重而道远,许多奇奇怪怪的现象背后都是简单的原理,搞懂了原理一切疑惑都能得到解答,如本文中提到的关于遍历带来的奇怪现象,其实就是声明式布局对于列表处理的通病,常用的解决方式就是通过声明主键来让组件最大程度优化性能。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
打赏
0
0
0
0
1
分享
相关文章
我的C++奇迹之旅:值和引用的本质效率与性能比较2
我的C++奇迹之旅:值和引用的本质效率与性能比较
我的C++奇迹之旅:值和引用的本质效率与性能比较1
我的C++奇迹之旅:值和引用的本质效率与性能比较
深度探秘:运用 Node.js 哈希表算法剖析员工工作时间玩游戏现象
在现代企业运营中,确保员工工作时间高效专注至关重要。为应对员工工作时间玩游戏的问题,本文聚焦Node.js环境下的哈希表算法,展示其如何通过快速查找和高效记录员工游戏行为,帮助企业精准监测与分析,遏制此类现象。哈希表以IP地址等为键,存储游戏网址、时长等信息,结合冲突处理与动态更新机制,确保数据完整性和时效性,助力企业管理层优化工作效率。
35 3
《Effective C++ 改善程序与设计的55个具体做法》 第一章 笔记
《Effective C++ 改善程序与设计的55个具体做法》 第一章 笔记
|
9月前
【从浅入深,全面掌握数组的操作与优化技巧】
【从浅入深,全面掌握数组的操作与优化技巧】
|
8月前
|
C++初阶学习第十一弹——探索STL奥秘(六)——深度刨析list的用法和核心点
C++初阶学习第十一弹——探索STL奥秘(六)——深度刨析list的用法和核心点
71 7
二分查找算法的细节刨析 --适合有基础的朋友阅读
二分查找算法的细节刨析 --适合有基础的朋友阅读
01【C语言 & 趣味算法】百钱百鸡问题(问题简单,非初学者请忽略叭)。请注意算法的设计(程序的框架),程序流程图的绘制,算法的优化。
01【C语言 & 趣味算法】百钱百鸡问题(问题简单,非初学者请忽略叭)。请注意算法的设计(程序的框架),程序流程图的绘制,算法的优化。
01【C语言 & 趣味算法】百钱百鸡问题(问题简单,非初学者请忽略叭)。请注意算法的设计(程序的框架),程序流程图的绘制,算法的优化。

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等