往期知识点整理
- 鸿蒙(HarmonyOS)北向开发知识点记录~
- 【鸿蒙性能优化】基于ArkUI的冷启动加载完成时延问题分析思路&案例
- 【鸿蒙性能优化】基于List的滑动过程卡顿率问题分析&案例
- 【鸿蒙性能优化】基于ArkUI启动冷启动过程最大连续丢帧数问题分析思路&案例
- 【鸿蒙性能优化】冷启动响应时延问题分析思路&案例
- 【鸿蒙性能优化】横竖屏切换开发实践
- 【鸿蒙性能优化】基于AOP的代码插桩
- 【鸿蒙性能优化】基于原生能力的设备唯一ID方案
- 【鸿蒙性能优化】基于Camera Kit,获取相机流数据传递给native,进行压缩编码
- 【鸿蒙性能优化】基于measure实现的文本测量
- 鸿蒙(HarmonyOS)应用开发之性能优化实战-组件复用
- 持续更新中……
若开发者的应用中存在以下场景,并成为UI线程的帧率瓶颈,应该考虑使用组件复用机制提升应用性能:
- 滑动场景下对同一类自定义组件的实例进行频繁的创建与销毁。
- 反复切换条件渲染的控制分支,且控制分支中的组件子树结构比较复杂。
组件复用生效的条件是:
- 自定义组件被@Reusable装饰器修饰,即表示其具备组件复用的能力。
- 在一个自定义组件(父)下创建出来的具备组件复用能力的自定义组件(子),在可复用自定义组件从组件树上移除之后,会被加入到其父自定义组件的可复用组件缓存中。
- 在一个自定义组件(父)下创建可复用的子组件时,若可复用子节点缓存中有对应类型的可复用子组件的实例,会通过更新可复用子组件的方式,快速创建可复用子组件。
约束限制
- @Reusable标识自定义组件具备可复用的能力,它可以被添加到任意的自定义组件上,但是开发者需要小心处理自定义组件的创建流程和更新流程以确保自定义组件在复用之后能展示出正确的行为。
- 可复用自定义组件的缓存和复用只能发生在同一父组件下,无法在不同的父组件下复用同一自定义组件的实例。例如,A组件是可复用组件,其也是B组件的子组件,并进入了B组件的可复用组件缓存中,但是在C组件中创建A组件时,无法使用B组件缓存的A组件。
- @Reusable装饰器只需要对复用子树的根节点进行标记。例如:自定义组件A中有一个自定义子组件B,若需要复用A与B的子树,只需要对A组件添加@Reusable装饰器。
- 可复用自定义组件中嵌套自定义组件,如果想要对嵌套的子组件的内容进行更新,需要实现对应子组件的aboutToReuse生命周期回调。例如:A组件是可复用的组件,B是A中嵌套的子组件,要想实现对A组件中的B组件内容进行更新,需要在B组件中实现aboutToReuse生命周期回调。
- 自定义组件的复用带来的性能提升主要体现在节省了自定义组件的JS对象的创建时间并复用了自定义组件的组件树结构,若应用开发者在自定义组件复用的前后使用渲染控制语法显著的改变了自定义组件的组件树结构,那么将无法享受到组件复用带来的性能提升。
- 组件复用仅发生在存在可复用组件从组件树上移除并再次加入到组件树的场景中,若不存在上述场景,将无法触发组件复用。例如,使用ForEach渲染控制语法创建可复用的自定义组件,由于ForEach渲染控制语法的全展开属性,不能触发组件复用。
- 组件复用当前不支持嵌套使用。即在可复用的组件的子树中存在可复用的组件,可能导致未定义的结果。
生命周期
可复用组件从C++侧的组件树上移除时,自定义组件在ArkUI框架native侧的CustomNode会被挂载到其对应的JSView上。复用发生之后,CustomNode被JSView引用,并触发ViewPU上的aboutToRecycle方法,ViewPU的实例将会被RecycleManager引用。
可复用组件从RecycleManager中重新加入组件树时,会调用前端ViewPU对象上的aboutToReuse生命周期回调。
接口说明
组件的生命周期回调,在可复用组件从复用缓存中加入到组件树之前调用,可在其中更新组件的状态变量以展示正确的内容,入参的类型与自定义组件的构造函数入参相同。
aboutToReuse?(params: {
[key: string]: unknown }): void;
组件的生命周期回调,在可复用组件从组件树上被加入到复用缓存之前调用。
aboutToRecycle?(): void;
开发者可以使用reuseId为复用组件分配复用组,相同reuseId的组件会在同一个复用组中复用。
reuseId(id: string);
Reusable装饰器,用于声明组件具备可复用的能力。
declare const Reusable: ClassDecorator;
示例:
// xxx.ets
class MyDataSource implements IDataSource {
private dataArray: string[] = [];
private listener: DataChangeListener | undefined;
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): string {
return this.dataArray[index];
}
public pushData(data: string): void {
this.dataArray.push(data);
}
public reloadListener(): void {
this.listener?.onDataReloaded();
}
public registerDataChangeListener(listener: DataChangeListener): void {
this.listener = listener;
}
public unregisterDataChangeListener(listener: DataChangeListener): void {
this.listener = undefined;
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i < 1000; i++) {
this.data.pushData(