当前位置: 首页 > news >正文

【HarmonyOS】性能优化——组件的封装与复用

1.组件动态创建

动态创建组件指的是在build生命周期前进行组件的创建。
这样做不仅可以节省组件创建的时间,还可以将独立的逻辑进行封装,有助于应用模块化开发。

1.1预创建原理

在声明式开发中,组件只能在build环节中被创建,无法在其他生命周期中被创建。ArkUI框架提供的自定义节点能力可以实现组件的预加载,预加载的组件可以进行属性和布局的设置。之后在页面加载时使用

1.2使用FrameNode自定义节点在动态布局下的优势

1.2.1减少自定义组件创建开销

在使用声明式开发范式中,使用ArkUI的自定义组件对节点树中的每个节点进行定义的效率不高

因为每个节点在ArkTS引擎中都需要分配内存空间来存储应用程序的自定义组件和状态变量,在节点的创建中,还必须执行组件ID、组件闭包、状态变量之间依赖关系的收集

使用FrameNode,则可以避免创建自定义组件对象和状态变量对象,无需进行依赖收集,提升组件的创建速度。

使用@Component创建自定义自定义组件实际上就是创建了一个自定义组件对象,对象实例中包括了@State、@Link等状态变量;同时UI框架还要执行依赖收集,即分析这个组件对象内部的状态变量之间、状态变量与UI之间的关系,以建立一套响应式更新机制,比较耗时

1.2.2可以直接操作组件树

声明式开发范式无法直接调整组件实例间的结构关系,而使用FrameNode可以很方便的操控组件树,将一个节点移植到另一个地方
在这里插入图片描述

1.3动态添加、更新、删除组件

1.3.1动态添加

  1. 创建自定义节点(使用@Builder)
@Builder
function ItemView(num: number){Column() {Text(`这是第${num}个组件`)}.width('100%').height(50)
}
  1. 实现NodeController,用于自定义节点的创建、显示、更新等操作的管理,并负责将自定义节点挂载至NodeContainer上
class Params{num: number constructor(num: number) {this.num = num;}
}
class ItemNodeControl extends NodeController{private itemNode: BuilderNode<[Params]> | null = null;private num: number = 0;constructor(num: number) {super()this.num = num;}makeNode(uiContext: UIContext): FrameNode | null {return null;}
}
  1. 使用NodeController的makeNode方法,该方法会在NodeController实例将要绑定NodeContainer时触发,将返回的Frame节点挂载至NodeContainer
makeNode(uiContext: UIContext): FrameNode | null {//首先构建BuilderNode实例this.itemNode = new BuilderNode(uiContext);//调用BuilderNode的build方法,创建组件树this.itemNode.build(wrapBuilder<[Params]>(ItemBuilder), new Params(this.num))//返回组件树的实体节点return this.itemNode.getFrameNode();}
  1. 使用NodeContainer显示自定义节点
@Local num: number = 0;private itemNodeControl: ItemNodeControl = new ItemNodeControl(this.num);build() {Column() {NodeContainer(this.itemNodeControl).width('100%').height(50)}.width('100%').height('100%')}

1.3.2动态删除

使用条件控制语句可以动态控制NodeContainer节点的移除和显示

if(){//....
}else{//...
}

动态更新

使用NodeController的makeNode方法和rebuild方法可以切换NodeContainer上绑定的节点

rebuild方法会触发makeNode方法,刷新NodeContainer上显示的节点,若makeNode返回节点为null,则会移除NodeContainer下挂载的节点

replaceBuilderNode(newNode: BuilderNode<Object[]>){this.itemNode = newNode;this.rebuild()}

1.4NodeController的生命周期

  • makeNode:必须重写的方法,用于构建节点树,在即将绑定NodeContainer前调用
  • aboutToResize:当相应的NodeContainer在Measure阶段时调用,入参为节点布局大小
  • aboutToAppear:当相应的NodeContainer在onAppear时调用
  • aboutToDisappear:当相应的NodeContainer在onDisappear时调用
  • onTouchEvent:当相应的NodeContainer在收到Touch事件时调用

2.组件复用

2.1概述

组件复用是指当自定义组件从组件树上移除后被放入缓存池,后续在创建同类型的组件节点时,直接复用缓存池中的组件对象

合理使用可复用组件的好处:

  • 可以避免频繁创建和销毁对象,减少CG次数
  • 另一方面,复用缓存中的组件爱你可以直接更换数据后直接显示,与创建新视图相比,降低了计算开销,提升了显示效率

2.2同一父组件之中实现组件复用

2.2.1实现原理

ArkUI提供了@Reusable来实现自定义组件的复用
在这里插入图片描述

  1. 被@Reusable装饰的自定义组件listItem列表项,在画出屏幕一定范围后,被从组件树上移除,组件的对象实例被放入CustomNode虚拟节点中
  2. 在滑动过程中,列表的RecycleManager(回收管理)会将这些CustomNode虚拟节点回收,根据复用表示reuseId分组,形成CachedRecycleNodes,即自定义组件的复用缓存池
  3. 当新的listItem需要在列表上显示时,RecycleManager会优先从复用缓存池里查找对应reuseId的节点,然后将数据绑定到该节点上,并将该节点添加至组件树中

当未使用reuseId属性划分组件时,会默认使用组件名划分

  • @Reusable装饰的组件需要布局在同一个父自定义组件下才能实现缓存复用

2.2.2不同场景下的使用

列表项结构类型相同

实现步骤:

  1. 将列表项封装为自定义组件,并使用@Reusable装饰
  2. 在组件的aboutToReuse方法中实现数据绑定逻辑
  // update data in aboutToReuse methodaboutToReuse(params: Record<string, Object>): void {this.title = params.title as string;this.from = params.from as string;this.tail = params.tail as string;}
  1. 在LazyForEach中使用组件时,设置reuseId

列表项结构类型不同

实现步骤:

  1. 将不同类型的列表项分别封装为自定义组件,添加@Reusable装饰
  2. 在组件内的aboutToResuse回调中进行数据绑定
  3. 在设置reuseId时,根据不同类型的列表项,使用if分别设置reuseId
 LazyForEach(this.dataSource, (item: ItemData) => {if (item.type === 0) {TextTypeItemView({ item: item }).reuseId('text_item_id')} else if (item.type === 1) {ImageTypeItemView({ item: item }).reuseId('image_item_id')} else if (item.type === 2) {ThreeImageTypeItemView({ item: item }).reuseId('three_image_item_id')}

组件内子组件可拆分组合

实现步骤:

  1. 将组件可拆分的部分都封装成一个子组件,并添加@Reusable装饰
    如将顶部,内容部,底部分别封装成一个子组件
  2. 进行数据绑定
  3. 实现不同组合对应的@Builder函数
  4. 在LazyForEach中,根据不同的业务逻辑,选择对应类型的@Builder函数

2.3不同父组件之间实现组件复用

2.3.1实现原理

由于复用缓存池需要在同一父组件中,此时需要自定义一个全局的复用缓存池NodePool,利用BuilderNode的节点复用能力,根据页面的状态动态创建、回收、复用子组件,实现跨页面的多个列表间的列表项复用

在ArkUI中,采用Swiper+List+复用缓存池来实现这个功能,由于Swiper中的每个List的列表项的父组件都是List,所以当切换List时,无法直接服用上一个页面的列表项,所以需要使用一个总的复用缓存池

之所以不使用Tab+List,是因为Tabs不支持使用LazyForEach,只能使用ForEach,一次性将TabContent全部创建,子页面切换时也不会触发aboutToDisappear回调,因此不存在组件复用的可能
在这里插入图片描述

  1. 在列表项的aboutToDisAppear回调中,根据NodeItem的type类型,存入NodePool中的type类型对应的集合中
  2. 每次需要创建自定义组件时,优先根据type类型查找响应的NodeItem对象,若未查找到则新建一个NodeItem
  3. 组件实体随NodeContainer的生命周期显示时,会执行数据更新,以实现组件的复用

2.3.2使用示例

  1. 实现NodeItem类,继承NodeController并实现makeNode方法

  2. 使用单例模式实现复用缓存池NodePool工具类,该类需要统一管理组件的复用逻辑

    a. 需要实现getNode方法,根据type的不同获取对应的NodeItem方法,未找到则新建

    b. 实现缓存回收方法recycleNode方法,根据type类型存入相应的集合

在getNode方法中,如果找到的NodeItem父节点不为空,需要继续遍历查找下一个有效的NodeItem对象
在reclycle方法中,需要将NodeItem对象的属性重置,使节点内容还原,避免复用显示异常

  1. 将NodeItem包装成为自定义组件,在相应的生命周期中进行缓存、回收、复用
  aboutToAppear(): void {//复用this.nodeItem = NodePool.getInstance().getNode(this.type, this.item, this.builder!)!;}aboutToRecycle(): void {//回收this.nodeItem?.node?.recycle();}aboutToReuse(params: ESObject): void {//缓存this.nodeItem?.node?.reuse(params);}
  1. 使用@Builder将其包装并对外export
  2. 在列表的LazyForEach中,将@Builder函数wrapBuilder后作为参数传递给NodeItem的自定义组件

2.3.3使用onldle方法预创建组件

有时用户在首次进入页面时耗时可能较高,因为在第一次进入时,组件复用池中没有缓存,所有组件都需要创建,为了优化首次进入的耗时,可以预创建组件,即将组件对象提前放入复用缓存池中

当组件的数量较多时,集中预创建本身也耗时较长,容易造成主线程阻塞。UI提供了onldle接口,该接口会返回每一帧帧尾的空闲时间,可以将组件的预创建分配到每一帧帧尾的空闲时间执行,避免集中运行阻塞主线程

  1. 在NodePool工具类中实现预创建preBuild方法:新建NodeItem实例,设置builder等属性,并调用recycleNode方法提前放入缓存池中
  2. 继承FrameCallback实现帧回调类,在构造方法中传入预创建组件的相关参数,并实现onldle方法
  • 系统会通过onldle回调,将帧尾空闲时间通过参数idleTimeInNano传递出来,可根据单个组件的预创建耗时,设置预创建的剩余空间上限

    i. 当剩余的空闲时间足够创建组件时,则在这一帧中进行组件的预创建,并不断更新当前帧的剩余空闲时间

    ii. 若当前帧的空闲时间不足创建组件,则调用postFrameCallback方法,将创建传递到下一帧,继续进行剩余组件的预创建

  1. 在首页的aboutToAppear回调中执行context.postFrameCallback方法,开启帧回调
  aboutToAppear(): void {let dataArray: ItemData[] = [];dataArray.push(...genMockItemData(100))let context = this.getUIContext();context.postFrameCallback(new IdleCallback(context, dataArray));}
http://www.dtcms.com/a/617718.html

相关文章:

  • 低代码平台的性能优化:解决页面卡顿、加载缓慢问题
  • 开源工程笔记:gitcode/github与性能优化
  • 微页制作网站模板手机上自己做网站吗
  • 基于51单片机的8路简易抢答器
  • Java设计模式精讲从基础到实战的常见模式解析
  • 柯美C654e打印机扫描复印有点画,怎么解决?
  • Vibe Coding之道:从Hulk扩展程序看Prompt工程的艺术
  • 【语义分割】12个主流算法架构介绍、数据集推荐、总结、挑战和未来发展
  • 宜兴市的城乡建设管理局网站泉州全网营销
  • Spring中使用Async进行异步功能开发实战-以大文件上传为例
  • 网络安全 | 深入解析XSS攻击与防御实战
  • 怎么做宇宙网站为何有的网站打不开
  • 做的网站文字是乱码wordpress upgrade文件夹
  • day1江协科技
  • Java案例拆解:junit/jvm一步到位
  • **论文初稿撰写工具2025推荐,高效写作与智能辅助全解析*
  • 三级供应链竞合博弈模拟
  • Apache Doris 自动分区:如何应对分布式环境下的复杂并发挑战|Deep Dive
  • 岭回归——附MATLAB代码
  • 深入分析线程池
  • 宁波大型网站建设大连网站开发公司电话
  • Qt 使用QAMQP连接RabbitMQ
  • 怎么写代码自己制作网站化妆品网站建设模板
  • 腾讯二面:如何保证MQ消息不丢失?重复消费如何保证幂等,本地消息表配合MQ实现最终一致性?
  • RocketMQ生产者多种API实战使用
  • UI设计公司审美积累|办公类软件界面设计巧思,效率与视觉的双重升级
  • 力扣1513——仅含 1 的子串数
  • Kali Linux 中对某(靶机)监控设备进行漏洞验证的完整流程(卧室监控学习)
  • 将LabelMe工具目标检测标注生成的json文件转换成COCO json格式
  • 什么是求解器?