Bootstrap

HarmonyOS Next 关于页面渲染的性能优化方案

HarmonyOS Next 关于页面渲染的性能优化方案

HarmonyOS Next 应用开发中,用户的使用体验至关重要。其中用户启动APP到呈现页面主要包含三个步骤:

  1. 框架初始化
  2. 页面加载
  3. 布局渲染

image-20241030202356771

页面加载布局渲染中,主要包含了6个环节:

  1. 执行页面文件
  2. 生成页面节点树
  3. 页面节点树挂载
  4. 布局
  5. 渲染
  6. 展示

页面节点树挂载的速度取决于节点的数量,我们可以理解给1个自定义组件在渲染时,后端同时会生成一个对应的

节点。该节点后期会用来diff

渲染的速度取决于布局属性。如果布局属性越复杂、冗余。那么就越慢。

节点的数量优化

HarmonyOS Next 会根据自定义节点的数量在后端生成对应的节点。那么如果我们在实际开发中,可以考虑尽量的将自定义组件的数量减少,替换成 @Builder 自定义构建函数。

那么哪些自定义节点可以替换成**@Builder**自定义构建函数呢,看下表:

分类自定义组件@Builder
复用布局结构支持支持
复用样式支持支持
导出使用支持不支持
生命周期支持不支持
状态管理支持不支持

所以,当我们对于封装的需求,不需要导出使用、不需要使用生命周期、不需要独立的状态管理时。就可以使用**@Builder**来代替自定义组件。

@Builder的基本使用

image-20241030204135638

@Entry
@Component
struct Index {
  @Builder
  CustomBtn(text:string){
    Button(text)
      .width(100)
      .height(50)
      .linearGradient({
        colors:[[Color.Black,0],[Color.Red,1]]
      })
  }

  build() {
    Column({space:10}){
      this.CustomBtn("登录")
      this.CustomBtn("注册")

    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

自定义组件的基本使用

image-20241030204135638

@Component
struct CustomBtn {
  text: string = ""

  build() {
    Button(this.text)
      .width(100)
      .height(50)
      .linearGradient({
        colors: [[Color.Black, 0], [Color.Red, 1]]
      })
  }
}

@Entry
@Component
struct Index {
  build() {
    Column({ space: 10 }) {
      CustomBtn({text:"登录"})
      CustomBtn({text:"注册"})

    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

布局属性的优化

这里的优化,主要是指性能的优化,也就是用户体验的优化,不是对于开发者来讲的开发体验的优化。

HarmonyOS Next 有提供 @Styles@Extends 来实现代码层面的优化,也就是样式代码的简单封装。

但是无论是用户层面的优化和代码层面的优化。@Styles 和 @Extends 都存在一定的限制。因此HarmonyOS

Next 又推出了 AttributeModifierAttributeUpdater(AttributeUpdater 是AttributeModifier的继承 )

AttributeModifier

  1. AttributeModifier是一个接口,需要我们主动实现它相关的方法。如默认态(Normal)、按压态(Pressed)、焦点态(Focused)、禁用态(Disabled)、选择态(Selected)
  2. AttributeModifier 可以实现样式属性的按需注册
  3. 支持和@Observed和@ObjectLink配套使用

AttributeModifier 基本使用

  1. 定义MyButtonModifier类,继承AttributeModifier接口,并且声明是对Button进行的样式属性封装

    export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
    
  2. MyButtonModifier中声明变量,用来注册不同的样式属性

    isDark: boolean = false
    
  3. 定义正常态的样式 (applyNormalAttribute 是接口AttributeModifier中定义的 )

      applyNormalAttribute(instance: ButtonAttribute): void {
        if (this.isDark) {
          instance.backgroundColor(Color.Black)
            .fontColor(Color.White)
            .border({
              width:10,
              color:Color.Brown
            })
            .borderRadius(20)
            .padding(10)
            .margin(20)
        } else {
          instance.backgroundColor(Color.White)
            .fontColor(Color.Black)
        }
      }
    

    image-20241030221519493

  4. 组件中开始复用(完整代码)

    export class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
      isDark: boolean = false
      constructor(dark?: boolean) {
        this.isDark = !!dark
      }
      applyNormalAttribute(instance: ButtonAttribute): void {
        if (this.isDark) {
          instance.backgroundColor(Color.Black)
            .fontColor(Color.White)
            .border({
              width:10,
              color:Color.Brown
            })
            .borderRadius(20)
            .padding(10)
            .margin(20)
        } else {
          instance.backgroundColor(Color.White)
            .fontColor(Color.Black)
        }
      }
    }
    
    
    @Entry
    @Component
    struct attributeDemo {
      @State modifier: MyButtonModifier = new MyButtonModifier(false);
    
      build() {
        Row() {
          Column() {
            Button("Button")
              // 注册属性
              .attributeModifier(this.modifier)
              .onClick(() => {
                // 点击切换
                this.modifier.isDark = !this.modifier.isDark
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    
  5. 效果

    PixPin_2024-10-30_22-18-52

AttributeModifier 其他状态

多态样式中除了默认态(Normal)还有 、按压态(Pressed)、焦点态(Focused)、禁用态(Disabled)、选择态(Selected)。我们一并实现。

  // 按压
  applyPressedAttribute(instance: ButtonAttribute): void {
    instance
      .backgroundColor(Color.Red)
  }
  // 获得焦点
  applyFocusedAttribute(instance: ButtonAttribute): void {
  }
  // 选择
  applySelectedAttribute(instance: ButtonAttribute): void {
  }
  // 禁用
  applyDisabledAttribute(instance: ButtonAttribute): void {
  }

PixPin_2024-10-30_22-25-22

搭配 @Observed和@ObjectLink

上述案例中,样式的变更是根据 变量 isDark来实现的。如果想要根据对象中某个属性来实现样式的变更。我们可以搭配@Observed和@ObjectLink

对象嵌套对象

以下代码主要利用了 @Observed和@ObjectLink 可以监听深层次属性的改变,然后当深层次属性改变后,触发

AttributeModifier 跟随改变。

  1. 声明子类 Son,代表深层次属性的载体

    @Observed
    class Son {
      // 控制样式切换的关键变量
      isShow: boolean = false
    }
    
  2. 使用 @Observed 修饰 Person类 (父类),拥有Son子类

    @Observed
    class Person {
      son: Son = new Son()
    }
    
  3. 声明 BtnModifier 类,需要实现 AttributeModifier 接口,实现样式优化和复用。接收 son属性。用来响应状态变化

    class BtnModifier implements AttributeModifier<ButtonAttribute> {
      son: Son
    
      constructor(son: Son) {
        this.son = son
      }
    
      applyNormalAttribute(instance: ButtonAttribute): void {
        if (this.son.isShow) {
          instance.backgroundColor(Color.Red)
        } else {
          instance.backgroundColor(Color.Green)
        }
      }
    }
    
  4. 完整代码

    import { promptAction } from '@kit.ArkUI';
    
    // 定义一个名为 'BtnModifier' 的类,实现对 'ButtonAttribute' 的属性修改
    class BtnModifier implements AttributeModifier<ButtonAttribute> {
      // 存储一个 'Son' 类型的实例
      son: Son;
    
      // 构造函数,接收一个 'Son' 类型的参数并初始化 'son' 属性
      constructor(son: Son) {
        this.son = son;
      }
    
      // 应用普通属性的方法,接收一个 'ButtonAttribute' 类型的实例作为参数
      applyNormalAttribute(instance: ButtonAttribute): void {
        // 如果 'son' 的 'isShow' 属性为 true,则将按钮背景颜色设置为红色
        if (this.son.isShow) {
          instance.backgroundColor(Color.Red);
        } else {
          // 否则将按钮背景颜色设置为绿色
          instance.backgroundColor(Color.Green);
        }
      }
    }
    
    // 使用 '@Observed' 装饰器标记的类,表示该类的变化可以被观测到
    @Observed
    class Son {
      // 定义一个布尔类型的属性 'isShow',初始值为 false
      isShow: boolean = false;
    }
    
    // 使用 '@Observed' 装饰器标记的类,表示该类的变化可以被观测到
    @Observed
    class Person {
      // 创建一个 'Son' 类型的实例并初始化
      son: Son = new Son();
    }
    
    @Component
    struct CustomBtn {
      // 使用 '@ObjectLink' 装饰器标记的属性,表示与外部对象的链接
      @ObjectLink
      son: Son;
      // 可空的 'BtnModifier' 类型属性
      modify: BtnModifier | null = null;
    
      // 在组件即将出现时执行的方法
      aboutToAppear(): void {
        // 创建一个新的 'BtnModifier' 实例并赋值给 'modify' 属性
        this.modify = new BtnModifier(this.son);
      }
    
      // 构建组件的方法
      build() {
        // 创建一个按钮,并将按钮的文本设置为 'son.isShow' 的字符串表示形式
        Button(this.son.isShow.toString())// 设置按钮的属性修改器为 'modify'
          .attributeModifier(this.modify);
      }
    }
    
    @Entry
    @Component
    struct Index {
      // 使用 '@State' 装饰器标记的属性,表示该属性的变化会触发组件的重新渲染
      @State
      person: Person = new Person();
    
      build() {
        // 创建一个列容器
        Column() {
          // 创建一个自定义按钮组件,并传入 'person.son' 作为参数
          CustomBtn({ son: this.person.son })// 为按钮添加点击事件处理函数
            .onClick(() => {
              // 切换 'person.son.isShow' 的值
              this.person.son.isShow = !this.person.son.isShow;
              // 显示一个提示信息
              promptAction.showToast({ message: `${this.person.son.isShow}` });
            });
        }
        .width("100%")
        .height("100%")
        .justifyContent(FlexAlign.Center);
      }
    }
    
    

    PixPin_2024-10-30_23-36-42

数组嵌套对象

数组嵌套对象的写法类似上面示例,但是可以通过简单的一些编程技巧来进一步优化。如单例

// 定义一个名为 'BtnModifier' 的类,实现对 'ButtonAttribute' 的属性修改
class BtnModifier implements AttributeModifier<ButtonAttribute> {
  // 静态变量,用于存储单例实例
  static instance: BtnModifier;
  // 表示是否在交谈的布尔属性,初始值为 false
  isTalk: boolean = false;

  // 静态方法,用于获取单例实例
  static getInstance(): BtnModifier {
    // 如果单例实例不存在,则创建一个新的实例
    if (!BtnModifier.instance) {
      BtnModifier.instance = new BtnModifier();
    }
    // 返回单例实例
    return BtnModifier.instance;
  }

  // 设置 isTalk 属性的方法
  setTalk(isTalk: boolean): BtnModifier {
    // 更新 isTalk 属性值
    this.isTalk = isTalk;
    // 返回当前实例,以便进行链式调用
    return this;
  }

  // 应用普通属性的方法,接收一个 'ButtonAttribute' 类型的实例作为参数
  applyNormalAttribute(instance: ButtonAttribute): void {
    // 如果 isTalk 为 true,则将按钮背景颜色设置为红色
    if (this.isTalk) {
      instance.backgroundColor(Color.Red);
    } else {
      // 否则将按钮背景颜色设置为绿色
      instance.backgroundColor(Color.Green);
    }
  }
}

// 使用 '@Observed' 装饰器标记的类,表示该类的变化可以被观测到
@Observed
class Person {
  // 用户名属性,初始值为 "人类"
  userName: string = "人类";
  // 表示是否在交谈的布尔属性,初始值为 false
  isTalk: boolean = false;
}

@Component
struct CustomBtn {
  // 使用 '@ObjectLink' 装饰器标记的属性,表示与外部对象的链接
  @ObjectLink
  person: Person;
  // 存储 'BtnModifier' 的实例,通过单例模式获取
  modify: BtnModifier = BtnModifier.getInstance();

  // 构建组件的方法
  build() {
    // 创建一个按钮,并将按钮的文本设置为 'person.userName'
    Button(this.person.userName)// 设置按钮的属性修改器,并根据 'person.isTalk' 的值设置是否在交谈状态
      .attributeModifier(this.modify.setTalk(this.person.isTalk));
  }
}

@Entry
@Component
struct Index {
  // 使用 '@State' 装饰器标记的属性,表示该属性的变化会触发组件的重新渲染
  @State
  personList: Person[] = [new Person(), new Person()];

  // 构建组件的方法
  build() {
    // 创建一个列容器
    Column() {
      // 遍历 'personList',为每个 'Person' 实例创建一个 'CustomBtn' 组件,并添加点击事件处理函数
      ForEach(this.personList, (person: Person) => {
        CustomBtn({ person: person })
          .onClick(() => {
            // 切换 'person.isTalk' 的值
            person.isTalk = !person.isTalk;
          });
      });
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center);
  }
}


PixPin_2024-10-30_23-39-43

AttributeModifier 和 @Styles 、@Extend的比较

能力@Styles@ExtendAttributeModifier
跨文件导出不支持不支持支持
通用属性设置支持支持支持
通用事件设置支持支持部分支持
组件特有属性设置不支持支持部分支持
组件特有事件设置不支持支持部分支持
参数传递不支持支持支持
多态样式支持不支持支持
业务逻辑不支持不支持支持

基于以上对比,可以看见 AttributeModifier 几乎可以满足以上所有场景。唯一缺点就是代码量稍多一些些。

接口定义

declare interface AttributeModifier<T> {

  applyNormalAttribute?(instance: T): void;
  
  applyPressedAttribute?(instance: T): void;
  
  applyFocusedAttribute?(instance: T): void;
  
  applyDisabledAttribute?(instance: T): void;
  
  applySelectedAttribute?(instance: T): void;

}

AttributeUpdater

如果设计大量的样式属性修改,如果都是基于状态变量,那么在实现修改前,还是会导致diff的对比,性能损耗验证。因此引入了 AttributeUpdater,它继承了AttributeModifier基本能力,还拓展了直接修改属性和组件构造函数的能力。用来根据单一状态来批量修改样式属性。

简单实用

  1. 声明 MyButtonUpdater 类,继承 AttributeUpdater
  2. 组件中实例化 MyButtonUpdater
  3. 直接修改组件样式属性
import { AttributeUpdater } from '@kit.ArkUI';

// 注意,这里是继承  AttributeUpdater 类
class MyButtonUpdater extends AttributeUpdater<ButtonAttribute> {
}

@Entry
@Component
struct attributeDemo {
  @State modifier: MyButtonUpdater = new MyButtonUpdater();

  build() {
    Row() {
      Column() {
        Button("直接修改批量样式属性")
          .attributeModifier(this.modifier)
          .onClick(() => {
            // 直接修改
            this.modifier.attribute?.backgroundColor(Color.Green).width(200).fontColor(Color.Red)
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

PixPin_2024-10-30_23-53-22

重新调用组件构造函数

提供了updateConstructorParams 接口,可以让我们重新调用该组件的构造函数。实现组件的重新渲染

  1. 继承 AttributeUpdater 类时,同时传入两个泛型 ButtonAttributeButtonInterface

    class MyButtonUpdater extends AttributeUpdater<ButtonAttribute,ButtonInterface> {
    
    }
    
  2. 直接调用要组件的构造函数 updateConstructorParams

    import { AttributeUpdater } from '@kit.ArkUI';
    
    // 注意,这里是继承  AttributeUpdater 类
    class MyButtonUpdater extends AttributeUpdater<ButtonAttribute,ButtonInterface> {
    
    }
    
    @Entry
    @Component
    struct attributeDemo {
      @State modifier: MyButtonUpdater = new MyButtonUpdater();
    
      build() {
        Row() {
          Column() {
            Button("重新渲染组件")
              .attributeModifier(this.modifier)
              .onClick(() => {
                this.modifier.updateConstructorParams("文本也可以改变")
              })
          }
          .width('100%')
        }
        .height('100%')
      }
    }
    

    PixPin_2024-10-31_00-00-34

接口定义

export declare class AttributeUpdater<T, C = Initializer<T>> implements AttributeModifier<T> {
  applyNormalAttribute?(instance: T): void;
  initializeModifier(instance: T): void;
  get attribute(): T | undefined;
  updateConstructorParams: C;
}

总结

后期如果要考虑实现样式复用,可以优先使用 AttributeModifier 和 AttributeUpdater

image-20241031000618657

作者

作者:万少

链接:https://www.nutpi.net/

來源:坚果派 著作权归作者所有。

商业转载请联系作者获得授权,非商业转载请注明出处。

;