HarmonyOS 7 ArkUI NodeAdapter 长列表的节点创建、缓存与回收机制【鸿蒙心迹】
列表里有 10 万条数据,难道真的需要创建 10 万个 UI 节点?

做长列表的时候,最开始的思路很简单:数据有多少条,就创建多少个节点。
结果真做的时候发现不对:10 万条数据,全部创建节点,内存直接爆了。滚动也卡得不行。
这时候才意识到:不是所有节点都要创建。屏幕上只能看到十几条,为什么要创建 10 万个?
一、先想清楚:屏幕上能看到多少
先做个简单的计算。屏幕高度是 800px,每个列表项高度是 80px。那屏幕上最多能看到多少条?
| 概念 | 数值 |
|---|---|
| 屏幕高度 | 800px |
| 列表项高度 | 80px |
| 一屏能看到 | 约 10 条 |
也就是说,不管你有 100 条还是 10 万条,屏幕上同时显示的就 10 条左右。
那剩下的 99990 条,为什么要创建节点?
二、LazyForEach 和 NodeAdapter 是什么
LazyForEach 是 ArkTS 自带的懒加载列表。它会根据可视区域,只创建你能看到的那几条。
NodeAdapter 是更底层的方式。它直接管理 FrameNode,你自己决定什么时候创建节点、什么时候销毁节点。
| 方案 | 层级 | 灵活性 |
|---|---|---|
| LazyForEach | ArkTS 声明式 | 高,自动管理 |
| NodeAdapter | Native 节点级 | 更高,自己管理 |
NodeAdapter 适合什么场景?比如自定义列表项很复杂、需要 Native 层面优化的情况。
三、一个 FrameNode 的一生
跟踪一个列表节点,它的一生是这样的:
- 列表滚动到这个位置,需要显示这个 item;
- 列表向 Adapter 请求这个位置的节点;
- Adapter 创建 FrameNode,设置数据;
- 节点挂到列表上,显示出来;
- 用户继续滚动,这个 item 滑出屏幕;
- 节点从列表上移除,进入缓存池;
- 用户往回滚,又滑到这个位置;
- 从缓存池拿出来,复用,重新设置数据;
- 一直不用了,缓存池满了,真正销毁。
这段代码解决什么问题: 实现 NodeAdapter。
文件: list/MyAdapter.ets
用途: 长列表节点管理
接入位置: 自定义长列表
import { NodeAdapter, FrameNode } from '@kit.ArkUI';
class MyAdapter extends NodeAdapter {
private data: string[] = [];
// 总共有多少个节点
totalNodeCount(): number {
return this.data.length;
}
// 请求创建节点
onCreateNode(index: number): FrameNode {
let node = new FrameNode();
node.setData(this.data[index]);
return node;
}
// 节点滑出屏幕
onRemoveNode(index: number, node: FrameNode): void {
// 不是自动释放,要自己处理
// 可以放进缓存池复用
}
}
这里最关键的是:节点滑出屏幕,不是自动释放。你要自己决定是放缓存池还是直接销毁。

四、缓存池是什么
缓存池就是把滑出屏幕的节点存起来,等需要的时候再拿出来用,不用重新创建。
为什么要缓存?因为创建节点是有开销的。每次滚动都重新创建,滚动就卡。
| 缓存大小 | 效果 |
|---|---|
| 0 | 每次都新建,卡 |
| 合适 | 复用节点,流畅 |
| 太大 | 内存占用高 |
缓存不是越大越好。缓存太多,内存就上去了。合适就好。
五、节点复用的时候要注意什么
节点从缓存池拿出来复用,不是直接用就行。它里面还是旧数据,要重新设置。
很多人出问题就是:节点复用了,但数据没更新,列表显示错了。
还有一个问题:节点里的状态。比如这个列表项有个开关,上次用户打开了,这次复用的时候,开关状态还在。不对,这是新的数据,状态要重置。

六、几个容易踩的坑
第一个坑:把"移出可视区域"理解成节点已经释放。不是,只是从列表上移除了,对象还在内存里。
第二个坑:所有滑出节点无限塞进 cachePool。缓存太大,内存爆了。
第三个坑:节点缓存回来以后仍保留旧业务状态。开关、输入框内容这些,都要重置。
第四个坑:Adapter 已接管子节点后继续直接 addChild。节点已经归 Adapter 管了,你别自己再加。
第五个坑:totalNodeCount 与真实数据数量不同步。数据变了,Adapter 不知道,列表就错了。
第六个坑:每次滚动都重新创建重量级 Native 资源。图片、Shader 这些,要复用,别每次新建。
第七个坑:为了减少创建次数把缓存开得无限大。内存代价太大。
七、和 LazyForEach 的区别
很多人以为 NodeAdapter 就是换一种写法的 LazyForEach。不对。
| 对比 | LazyForEach | NodeAdapter |
|---|---|---|
| 层级 | ArkTS 声明式 | Native 节点级 |
| 管理 | 系统自动 | 你自己管 |
| 灵活性 | 够用 | 更高 |
| 适合 | 普通列表 | 复杂自定义列表 |
NodeAdapter 不是替代 LazyForEach,是给你更底层的控制力。普通列表用 LazyForEach 就够了。

这次做长列表最大的体会是:长列表的核心不是"懒加载"这三个字,是节点生命周期管理。什么时候创建、什么时候缓存、什么时候复用、什么时候销毁,每一步都要想清楚。
真正做的时候,最容易忽略的不是怎么创建节点,而是节点复用和缓存管理。创建节点很简单,难的是怎么高效地复用,不浪费内存,也不浪费创建开销。
更多推荐

所有评论(0)