• 鸿蒙系列-如何使用好 ArkUI 的 @Reusable?


    如何使用好 ArkUI 的 @Reusable?

    OpenHarmony 组件复用机制

    在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为 系统组件,由开发者定义的称为 自定义组件

    在进行 UI 界面开发时,通常不是简单的将系统组件进行组合使用,而是需要考虑代码可复用性、业务逻辑与UI分离,后续版本演进等因素。因此,将UI和部分业务逻辑封装成自定义组件是不可或缺的能力。

    本文主要对组件复用做一些基本介绍,然后对 @Reusable做分析,进而帮助开发者理解设置前后的效果,以及我们会尝试寻找何时使用这个属性最佳,进而给开发者提出参考方案。

    为什么要组件复用?

    使用组件复用往往是解决资源短缺的问题,通过节约内存节省资源,简称开源节流。

    开源节流是我国理财的基本原则之一。开源指开拓财政收入来源,节流指尽可能减少不必要的支出,或少花钱多办事。

    荀子《富国》
    荀子在《富国》篇中说“故田野县鄙者,财之本也;垣窌仓廪者,财之末也;百姓时和,事业得叙者,货币之源也;等赋府库者,货之流也。故明君必养其和、节其流、开其源而时斟酌焉。”其意系指农业是创造财富的根本大业,聚财只是其结果,货币只是流通的手段,财政收入只能从流通中取得,故用扩大流通的方式来增加收入,对增加财富并无好处。

    同理,鸿蒙系统的内存是非常宝贵的,它不仅需要提供给底层系统维持系统正常运行,还要分配给网络、通知、后台任务、传感器、中断等一系列工作。那么我们在应用开发时就要想办法节约内存,为系统工作分忧。

    @Reusable组件复用装饰器

    在 ArkUI中,使用 @Component装饰器修饰组件,组件允许开发者组合使用系统组件、及其属性和方法,并通过状态变量的改变,来驱动UI的刷新。 @Reusable是修饰组件的属性,赋予组件重用的特性。
    在这里插入图片描述

    什么是@Reusable?

    @Reusable 是一个装饰器,它可以标识自定义组件具备可复用的能力,也可以被添加到任意的自定义组件上。并且也可以在跨平台提供响应的能力。整体来说,它是 ArkUI的一部分。

    它的定义如下:

    /**
     * Defining Reusable ClassDecorator.
     *
     * @syscap SystemCapability.ArkUI.ArkUI.Full
     * @crossplatform
     * @since 10
     */
    declare const Reusable: ClassDecorator;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以看出,Reusable 实际上是一个 ClassDecorator , 那么它的内部结构是如何的呢?

    从上图可以分析并查看 component构造函数:

        constructor(parent, localStorage, elmtId = -1) {
            super();
            ...
            this.recycleManager = undefined;
            ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    component的构造函数中我们发现,每一个 component持有一个 recycleManagerrecycleManager参与管理 Reusable 组件,那么 recycleManager是如何管理复用组建的呢?

    接下来我们再看它的后端实现逻辑:

    视图层的关系如下:

    视图层是通过js实现的,

    1. xxx
    2. xxx

    …todo

    我们在视图树的结构中发现一个环,众所周知,环会产生内存泄漏,所以在页面退出时,需要将 RecycleManager中持有的view全部手动释放,以此来破环。

    当组件被重用时,就会触发 aboutToReuse 方法:

    aboutToReuse?(params: { [key: string]: unknown }): void;
    
    
    • 1
    • 2

    在这个函数中就可以重载数据。

    连接层的关系如下:

    连接层是桥接 js 和 c++的中间层。

    我们在 js_ace_page.h文件中发现以下代码,可以验证持有关系。

    namespace OHOS::Ace::Framework {
    
    ...
    private:
    #ifndef NG_BUILD
        void SwapBackgroundDecoration(const RefPtr& transition);
    #endif
        ...
        RefPtr component_;
    ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    节点层的关系如下:

    节点层是通过c++实现的。

    1. xxx
    2. xxx

    …todo

    那么我们接下来看下组件复用发生和不发生时的区别:

    组件复用发生时:

    在复用组件在view显示后,组织结构如下:

    在c++
    构造树中,当@Reusable修饰的组建复用后,JSView强持有c++的view,page的实例被RecycleManager 管理持有。

    此时如果page被回收,由于 c++的view 被jsview强持有,就无法得到释放。所以需要在c++的析构中,对RecycleManager中所有的 复用组件释放,完成手动释放。

    组件复用不发生时:

    在复用组件从view移除后,组织结构发生如下变化:

    在c++
    构造树中,当@Reusable修饰的组建删除后,JSView不再强持有c++的view,此时c++的view被RecycleManager 管理持有,等待下一次被重用。

    此时如果page被回收,由于 c++的view 并没有被jsview强持有,就可以得到释放。

    。。todo

    实战:@Reusable 可以解决的问题:

    现在我们知道了 @Reusable 的基本功能,那么它在实际开发中可以解决什么问题呢?根据我的思考🤔️和整理,主要可以解决以下三类问题。

    问题一:解决组件嵌套过深:

    子组件通过@Prop接收父组件传递的数据,如果嵌套的层数过多,会导致深拷贝占用的空间过大以及GarbageCollection(垃圾回收),引起性能问题。通过 @Reusable可解决嵌套的层数过多的问题。

    那么 @Reusable是如何解决这个问题的?请看下文:

    我们在开发时往往会遇到多层组件嵌套的场景,通俗说就是view中套子view,子view中又套孙子view,以此类推。
    这样就会产生一个嵌套过深的问题参考,见下图:

    具体代码如下:

    @Entry
    @Component
    struct FatherView {
      @State vote: ClassE = new ClassE()
    
      build() {
        Column() {
          Button('change')
            .onClick(() => {
              this.vote.name = "I'm Father"
            })
          Child({ voteOne: this.vote })
        }
      }
    }
    
    @Component
    struct SonView {
      @Prop voteOne: ClassE
    
      build() {
        Column() {
          Button('change')
            .onClick(() => {
              this.vote.name = "I'am Son"
            })
          Child({ voteTwo: this.vote })
        }
      }
    }
    
    
    @Component
    struct SonView {
      @Prop voteTwo: ClassD
    
      build() {
        Column() {
          Button('change')
            .onClick(() => {
              this.vote.name = "I'am grandson"
            })
          Child({ voteThree: this.vote })
        }
      }
    }
    
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    在不断增加层次后,问题产生了:

    1. 由于层次过深,代码难以阅读,并且传递关系过于复杂,传递依赖链过长,不满足第三范式。
      第三范式:表中每一列的属性都不可再分,且非主属性完全依赖于主属性,且每个非主属性都不传递函数依赖于主属性。 https://en.wikipedia.org/wiki/Third_normal_form

    2. 可能触发GarbageCollection(垃圾回收机制),产生问题。

    具体什么条件会触发,暂无资料,还需要相关子系统提供源码分析🧐。

    那么在使用 @Reusable优化后的效果如下:

    具体代码如下:

    @Entry
    @Component
    struct FatherView {
      @State vote: ClassE = new ClassE()
    
      build() {
        Column() {
          Button('change')
            .onClick(() => {
              this.vote.name = "I'm Father"
            })
          Child({ voteOne: this.vote })
        }
      }
    }
    
    @@Reusable
    @Component
    struct SonView {
      @Prop voteOne: ClassE
    
      build() {
        Column() {
          Button('change')
            .onClick(() => {
              this.vote.name = "I'am Son"
            })
          Child({ voteTwo: this.vote })
        }
      }
    }
    
    @@Reusable
    @Component
    struct SonView {
      @Prop voteTwo: ClassD
    
      build() {
        Column() {
          Button('change')
            .onClick(() => {
              this.vote.name = "I'am grandson"
            })
          Child({ voteThree: this.vote })
        }
      }
    }
    
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    在使用 @Reusable优化后,问题解决了:

    1. 消除了传递依赖,代码层次结构更清晰了,对于组件间互相交互也更好处理,在深拷贝的传递链上已满足第三范式。

    2. 深度只有2层,100%避免了由于深度增加触发GarbageCollection(垃圾回收机制)的条件,避免产生问题。

    问题二:减少创建控件时间:

    。。todo

    问题三:使代码更优雅:

    。。todo

    总结:

    。。todo

    个人观点:

    。。todo

    相关扩展:

    。。todo

    – 缓冲池机制:

    传统的 LRU:

    什么是预读失败?

    什么是缓冲池污染?

  • 相关阅读:
    “蔚来杯“2022牛客暑期多校训练营5,签到题KBGHFCD
    laravel练习03
    MySQL 外键约束 多表联查 联合查询
    SpringBoot【运维实用篇】
    Redis 数据结构
    批量处理图片:轻松转换JPG到TIFF,优化你的图片管理“
    【附源码】Python计算机毕业设计食堂在线点餐系统小程序
    Util 应用框架 UI 全新升级
    Intel汇编语言程序设计(第7版)第四章编程练习题答案
    【51单片机】智能百叶窗项目
  • 原文地址:https://blog.csdn.net/gwh111/article/details/132581858