鸿蒙性能优化之布局优化

0 阅读5分钟

避免在生命周期函数执行耗时操作

  在build函数执行之前,将先执行aboutToAppear()生命周期回调函数。若在该函数中执行耗时操作,将阻塞UI渲染,增加UI主线程负担。可以将耗时操作放到子线程或者使用不耗时的函数。例如在aboutToAppear中应该避免使用ResourceManager的getXXXSync接口入参中直接使用资源信息,推荐使用资源id作为入参,推荐用法为:resourceManager.getStringSync($r('app.string.test').id)。第一种方式获取的是拷贝对象,发生了一次深拷贝,第二种方式直接获取原对象的引用。

使用@Builder方法代替自定义组件

  在ArkUI中使用自定义组件时,在build阶段将在后端FrameNode树创建一个相应的CustomNode节点,在渲染阶段时也会创建对应的RenderNode节点,如下图所示。 image.png   减少自定义组件的使用,尤其是自定义组件在循环中的使用,将成倍减少FrameNode节点树上CustomNode节点数量,有效缩短页面的加载和渲染时长。@Builder函数不会在后端FrameNode节点树上创建一个新的树节点。如果组件仅仅是用来纯展示,不需要更新,优先使用@Builder函数代替自定义组件。

按需注册组件属性

  组件每个属性保存在FrameNode节点上,组件设置了大量属性且该组件被大量使用,对应用的整体性能会产生较大影响。应按需注册组件属性,避免设置冗余属性。如果必须设置很多属性,可以考虑AttributeModifier动态注册组件属性的方式。动态注册组件属性可以实现差异更新属性,当组件创建或者更新时,重新执行组件的样式属性对象的更新接口。通过key找到对应的属性修改器对象进行差异对比,若有更新变化再通知native侧进行属性更新。

// 1.自定义属性修改器,该类实现了AttributeModifier接口
class RowModifier implements AttributeModifier<RowAttribute> {
  private customImage: ResourceStr = '';
  private static instance: RowModifier;

  constructor() {}

  setCustomImage(customImage: ResourceStr) {
    this.customImage = customImage;
    return this;
  }
  // 采用单例模式,避免为每个组件都创建一个新的修改器,增加创建产生的性能开销
  public static getInstance(): RowModifier {
    if (!RowModifier.instance) {
      RowModifier.instance = new RowModifier();
    }
    return RowModifier.instance;
  }
  // 2.实现AttributeModifier接口的applyNormalAttribute方法,自定义属性设置的逻辑
  applyNormalAttribute(instance: RowAttribute) {
    if (this.customImage) {
      instance.backgroundImage(this.customImage);
      instance.backgroundImageSize(ImageSize.Cover);
     } else {
      instance.backgroundColor(DEFAULT_BACKGROUND_COLOR);
      instance.justifyContent(FlexAlign.Center);
      // instance.padding(2)
      // instance.margin(2)
      // instance.opacity(1)
      // instance.clip(false)
      // instance.layoutWeight(1)
      // instance.backgroundBlurStyle(BlurStyle.NONE)
      // instance.alignItems(VerticalAlign.Center)
      // instance.borderWidth(1)
      // instance.borderColor(Color.Pink)
      // instance.borderStyle(BorderStyle.Solid)
      // instance.expandSafeArea([SafeAreaType.SYSTEM])
      // instance.rotate({ angle: 5 })
      // instance.responseRegion({x: 0})
      //instance.mouseResponseRegion({x: 0})
      // instance.constraintSize({minWidth: 25})
      // instance.hitTestBehavior(HitTestMode.Default)
      //instance.backgroundImagePosition(Alignment.Center)
      //instance.foregroundBlurStyle(BlurStyle.NONE)
    }
    instance.size({ width: 50, height: 50 });
    instance.borderRadius(25);
  }
}

@Component
struct Avatar {
  @ObjectLink user: User;

  build() {
    Row() {
      if (!this.user.avatarImage) {
        Text(this.user.name.charAt(0))
          .fontSize(28)
          .fontColor(Color.White)
          .fontWeight(FontWeight.Bold)
      }
    }
    // 3.将自定义RowModifier类作为参数传入,实现按需注册属性
    .attributeModifier(RowModifier.getInstance().setCustomImage(this.user.avatarImage))
  }
}

精简节点数

  移除冗余的节点,可能会在Row容器包含一个Row容器。如下代码,Row容器Row容器,这种嵌套实际是多余的,并且会给布局层次结构造成不必要的开销。

Row() {
  Row(){
    Image()
    Text()
  }
  Image()
}

  修改后的代码

Row() {
  Image()
  Text()
  Image()
}

合理使用布局容器组件

  • 在相同嵌套层级的情况下,如果多种布局方式可以实现相同布局效果,优选低耗时的布局,如使用Column、Row替代Flex实现相同的单行布局。
  • 在能够通过其他布局大幅优化节点数的情况下,可以使用高级组件替代,如使用RelativeContainer替代Row、Column实现扁平化布局,此时其收益大于布局组件本身的性能差距。
  • 仅在必要的场景下使用高耗时的布局组件,如使用Flex实现折行布局、使用Grid实现二维网格布局等。

利用布局边界减少布局计算

  如果组件的宽高不需要自适应,那就写死宽高。当其组件外部的容器尺寸发生变化时,组件本身的宽高固定,就不需要重新测量。

合理控制元素显示与隐藏

  使用Visibility.None、if条件判断等都能够元素显示与隐藏。如果频繁修改元素的显示与隐藏,通过visibility属性控制,可以省去组件创建的时间,直接进入渲染过程。交互次数很少的情况下,使用if条件判断来控制元素的显示与隐藏效果,对于内存有较大提升。

Scroll嵌套List场景下,给定List组件宽高

Scroll嵌套List时:

  • 给list组件设置宽高,只有布局区域内的子组件会参与布局。不设置宽高,所有子组件都会参与布局。
  • List使用ForEach加载子组件时,无论是否设置List的宽高,都会加载所有子组件。
  • List使用LazyForEach加载子组件时,没有设置List的宽高,会加载所有子组件,设置了List的宽高,会加载List显示区域内的子组件。

优先使用组件属性代替嵌套组件

  在实现文本浮层、按压遮罩或颜色叠加等场景时,通常会采用Stack布局嵌套组件的方式。实际上有些场景直接使用组件属性或借助系统API的能力就能实现,例如使用overlay属性可以实现浮层场景,使用ColorMetrics可以实现颜色叠加效果。直接使用组件属性可以减少Stack布局嵌套组件,减少组件节点数。