鸿蒙开发基础 - 关于生命周期钩子详解

396 阅读13分钟

1. 前言

什么是生命周期?这里先对生命周期的概念做一个解释

生命周期钩子(Lifecycle Hooks)是许多现代前端框架和库(如Vue.js、React、Angular等)中的一个核心概念。
这些钩子提供了一种在组件或指令的不同阶段执行自定义逻辑的方法。
它们允许开发者在特定时刻介入组件的生命周期,执行一些必要的操作,如数据获取、状态更新、DOM操作等

生命周期钩子的设计旨在帮助开发者更好地管理应用和组件的状态变化。在鸿蒙(HarmonyOS)开发中,UIAbility和组件同样遵循一套生命周期模型。

UIAbility生命周期钩子主要涉及应用的启动、运行和退出等阶段。而页面组件生命周期钩子则关注于页面创建、显示、隐藏和销毁等过程。
这里主要介绍常用的生命周期钩子,一共是6+5(+2)个,具体如下:

UIAbility生命周期钩子(6个):

  1. onCreate: 应用创建时调用。
  2. onDestroy: 应用销毁时调用。
  3. onWindowStageCreate: WindowStage创建时调用。
  4. onWindowStageDestroy: WindowStage销毁时调用。
  5. onForeground: 应用进入前台时调用。
  6. onBackground: 应用进入后台时调用。

页面组件生命周期钩子(5个):

  1. aboutToAppear: 页面创建时调用。
  2. aboutToDisappear: 页面销毁时调用。
  3. onPageShow: 页面准备展示时调用。
  4. onPageHide: 页面完全隐藏时调用。
  5. onBackPress: 用户返回操作时调用。

组件复用生命周期钩子(2个扩展)API10+

  1. aboutToReuse:复用组件从复用缓存中加入到组件树之前调用
  2. aboutToRecycle:复用组件从组件树上卸载进入复用缓存之前调用

理解并正确使用这些生命周期钩子对于构建性能良好、用户体验优秀的鸿蒙应用至关重要。它们允许我们在关键时刻执行必要的逻辑,如数据加载、资源释放、状态保存等,从而确保应用的流畅运行和资源的有效管理。
因为官网对于生命周期的讲解太过细碎,所以这里就对生命周期进行统一的整理和讲解,一起来看看吧~

2e59fed0ba3d555346793840ec38671a.jpg

2. UIAbility生命周期钩子

对于UIAbility生命周期的概述:
当用户打开、切换和返回到对应应用时,应用中的UIAbility实例会在其生命周期的不同状态之间转换。UIAbility类提供了一系列回调,通过这些回调可以知道当前UIAbility实例的某个状态发生改变,会经过UIAbility实例的创建和销毁,或者UIAbility实例发生了前后台的状态切换。


UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态,如下图所示。

image.png

  • Create状态为在应用加载过程中,UIAbility实例创建完成时触发,系统会调用onCreate()回调。可以在该回调中进行页面初始化操作,例如变量定义资源加载等,用于后续的UI展示。
  • UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStageWindowStage创建完成后会进入 onWindowStageCreate() 回调,可以在该回调中设置UI加载、设置WindowStage的事件订阅。
  • onWindowStageCreate() 回调中通过loadContent()方法设置应用要加载的页面,并根据需要调用on('windowStageEvent')方法订阅WindowStage事件(获焦/失焦、可见/不可见)。
  • 对应于 onWindowStageCreate() 回调。在UIAbility实例销毁之前,则会先进入 onWindowStageDestroy() 回调,可以在该回调中释放UI资源。
  • Destroy状态在UIAbility实例销毁时触发。可以在onDestroy()回调中进行系统资源的释放、数据的保存等操作。

例如调用terminateSelf()方法停止当前UIAbility实例,从而完成UIAbility实例的销毁;或者用户使用最近任务列表关闭该UIAbility实例,完成UIAbility的销毁。

image.png

image.png


参数文档参考:

以下是代码案例,官网文档参考 -> UIAbility组件生命周期

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // hilog:日志模块
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    /*
     * Create状态为在应用加载过程中,UIAbility实例创建完成时触发,系统会调用onCreate()回调。
     * 可以在该回调中进行页面初始化操作,例如变量定义资源加载等,用于后续的UI展示。
     * 这里有2个参数,want和launchParam
     * want:Want是对象间信息传递的载体,可以用于应用组件间的信息传递。
     * launchParam:启动参数,用来判断是什么方式启动的(用户启动,自启动,其他应用拉起,自动恢复等...)
     * */
    // 页面初始化...
  }

  onDestroy(): void {
    // hilog:日志模块
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
    /*
     * Destroy状态在UIAbility实例销毁时触发。可以在onDestroy()回调中进行系统资源的释放、数据的保存等操作。
     * 例如调用terminateSelf()方法停止当前UIAbility实例,从而完成UIAbility实例的销毁;
     * 或者用户使用最近任务列表关闭该UIAbility实例,完成UIAbility的销毁。
     * */
    // 系统资源的释放、数据的保存等
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    /*
     * windowStage:窗口管理器。管理各个基本窗口单元,即Window实例。
     * 可以使用windowStage.on来订阅窗口的状态事件(获焦/失焦、可见/不可见)
     * */
    /*try {
      windowStage.on('windowStageEvent', (data) => {
        let stageEventType: window.WindowStageEventType = data;
        switch (stageEventType) {
          case window.WindowStageEventType.SHOWN: // 切到前台
            console.info('windowStage foreground.');
            break;
          case window.WindowStageEventType.ACTIVE: // 获焦状态
            console.info('windowStage active.');
            break;
          case window.WindowStageEventType.INACTIVE: // 失焦状态
            console.info('windowStage inactive.');
            break;
          case window.WindowStageEventType.HIDDEN: // 切到后台
            console.info('windowStage background.');
            break;
          default:
            break;
        }
      });
    } catch (exception) {
      console.error('Failed to enable the listener for window stage event changes. Cause:' +
      JSON.stringify(exception));
    }*/

    /*
     * 使用windowStage.loadContent来设置UI加载的第一个页面
     * */
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
      // 注意,这里的时候,页面的UI资源已经加载完成,可以执行一些Ark框架中的一些核心方法
    });
  }

  onWindowStageDestroy(): void {
    // Main window is destroyed, release UI related resources
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
    /*
     * 在UIAbility实例销毁之前,则会先进入onWindowStageDestroy()回调。
     * 可以在该回调中释放UI资源。例如在onWindowStageDestroy()中注销获焦/失焦等WindowStage事件。
     * */
    // 释放UI资源
    // 例如在onWindowStageDestroy()中注销获焦/失焦等WindowStage事件
  }

  onForeground(): void {
    // Ability has brought to foreground
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
    /*
     * onForeground()回调,在UIAbility的UI可见之前,如UIAbility切换至前台时触发。
     * 可以在onForeground()回调中申请系统需要的资源,或者重新申请在onBackground()中释放的资源。
     * */
    // 申请系统需要的资源,或者重新申请在onBackground()中释放的资源
  }

  onBackground(): void {
    // Ability has back to background
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
    /*
     * onBackground()回调,在UIAbility的UI完全不可见之后,如UIAbility切换至后台时候触发。
     * 可以在onBackground()回调中释放UI不可见时无用的资源,或者在此回调中执行较为耗时的操作,例如状态保存等。
     * */
    // 释放UI不可见时无用的资源,或者在此回调中执行较为耗时的操作
    // 例如状态保存等
  }
}

3. 页面组件生命周期钩子

在开始之前,我们先明确自定义组件和页面的关系:

  • 自定义组件:@Component装饰的UI单元,可以组合多个系统组件实现UI的复用,可以调用组件的生命周期。
  • 页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。

页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:

  • onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
  • onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
  • onBackPress:当用户点击返回按钮时触发。

组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:

  • aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
  • aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。不建议在生命周期aboutToDisappear内使用async await,如果在生命周期的aboutToDisappear使用异步操作(Promise或者回调方法),自定义组件将被保留在Promise的闭包中,直到回调方法被执行完,这个行为阻止了自定义组件的垃圾回收。

生命周期执行的时序如下图: 官网文档参考 -> 页面组件生命周期

image.png

4. (扩展)组件复用生命周期钩子

若开发者的应用中存在以下场景,并成为UI线程的帧率瓶颈,应该考虑使用组件复用机制提升应用性能:

  1. 滑动场景下对同一类自定义组件的实例进行频繁的创建与销毁;
  2. 反复切换条件渲染的控制分支,且控制分支中的组件子树结构比较复杂。

组件复用生效的条件是:

  1. 自定义组件被@Reusable装饰器修饰,即表示其具备组件复用的能力;
  2. 在一个自定义组件(父)下创建出来的具备组件复用能力的自定义组件(子),在可复用自定义组件从组件树上移除之后,会被加入到其父自定义组件的可复用组件缓存中;
  3. 在一个自定义组件(父)下创建可复用的子组件时,若可复用子节点缓存中有对应类型的可复用子组件的实例,会通过更新可复用子组件的方式,快速创建可复用子组件。

约束限制

  1. @Reusable标识自定义组件具备可复用的能力,它可以被添加到任意的自定义组件上,但是开发者需要小心处理自定义组件的创建流程更新流程以确保自定义组件在复用之后能展示出正确的行为;
  2. 可复用自定义组件的缓存和复用只能发生在同一父组件下,无法在不同的父组件下复用同一自定义组件的实例。例如,A组件是可复用组件,其也是B组件的子组件,并进入了B组件的可复用组件缓存中,但是在C组件中创建A组件时,无法使用B组件缓存的A组件;
  3. @Reusable装饰器只需要对复用子树的根节点进行标记。例如:自定义组件A中有一个自定义子组件B,若需要复用A与B的子树,只需要对A组件添加@Reusable装饰器。
  4. 可复用自定义组件中嵌套自定义组件,如果想要对嵌套的子组件的内容进行更新,需要实现对应子组件的aboutToReuse生命周期回调。例如:A组件是可复用的组件,B是A中嵌套的子组件,要想实现对A组件中的B组件内容进行更新,需要在B组件中实现aboutToReuse生命周期回调。
  5. 自定义组件的复用带来的性能提升主要体现在节省了自定义组件的JS对象的创建时间并复用了自定义组件的组件树结构,若应用开发者在自定义组件复用的前后使用渲染控制语法显著的改变了自定义组件的组件树结构,那么将无法享受到组件复用带来的性能提升;
  6. 组件复用仅发生在存在可复用组件从组件树上移除并再次加入到组件树的场景中,若不存在上述场景,将无法触发组件复用。例如,使用ForEach渲染控制语法创建可复用的自定义组件,由于ForEach渲染控制语法的全展开属性,不能触发组件复用。
  7. 组件复用当前不支持嵌套使用。即在可复用的组件的子树中存在可复用的组件,可能导致未定义的结果。

开发建议

  1. 建议复用自定义组件时避免一切可能改变自定义组件的组件树结构和可能使可复用组件中产生重新布局的操作以将组件复用的性能提升到最高;
  2. 建议列表滑动场景下组件复用能力和LazyForEach渲染控制语法搭配使用以达到性能最优效果;
  3. 开发者需要区分好自定义组件的创建和更新过程中的行为,并注意到自定义组件的复用本质上是一种特殊的组件更新行为,组件创建过程中的流程与生命周期将不会在组件复用中发生,自定义组件的构造参数将通过aboutToReuse生命周期回调传递给自定义组件。例如,aboutToAppear生命周期和自定义组件的初始化传参将不会在组件复用中发生;
  4. 避免在aboutToReuse生命周期回调中产生耗时操作,最佳实践是仅在aboutToReuse中做自定义组件更新所需的状态变量值的更新;
  5. 无需在aboutToReuse中对@Link、@StorageLink、@ObjectLink、@Consume等自动更新值的状态变量进行更新,可能触发不必要的组件刷新。

性能收益
-> 点击查看 使用组件复用后的性能收益


组件复用生命周期钩子触发时序: image.png

代码案例:

@Entry
@Component
struct LifeCycle {
  @State message: string = 'Hello World';
  @State show: boolean = true

  aboutToAppear(): void {
    console.log("LifeCycle_aboutToAppear:", JSON.stringify("页面开始准备展示"))
  }

  aboutToDisappear(): void {
    console.log("LifeCycle_aboutToDisappear:", JSON.stringify("页面开始准备销毁"))
  }

  onPageShow(): void {
    console.log("LifeCycle_onPageShow:", JSON.stringify("页面进入了前台"))
  }

  onPageHide(): void {
    console.log("LifeCycle_onPageHide:", JSON.stringify("页面进入了后台"))
  }

  onBackPress(): boolean | void {
    console.log("LifeCycle_onBackPress:", JSON.stringify("用户返回了"))
  }

  build() {
    Column() {
      Button("展示组件")
        .onClick(() => {
          this.show = !this.show
        })
      if (this.show) {
        MyText({ message: this.message + Date.now() })
      }
    }.width("100%").height("100%").justifyContent(FlexAlign.Center)
  }
}


@Reusable
@Component
struct MyText {
  @State message: string = 'chiqingsan'

  aboutToAppear(): void {
    console.log("LifeCycle_aboutToAppear:", "子组件开始准备展示")
  }

  aboutToDisappear(): void {
    console.log("LifeCycle_aboutToDisappear:", "子组件开始准备销毁")
  }

  aboutToReuse(params: Record<string, ESObject>) {
    console.log("LifeCycle_aboutToReuse:", "组件准备挂载到组件树")
    console.log("LifeCycle_params:", JSON.stringify(params))
    this.message = params.message
  }

  aboutToRecycle(): void {
    console.log("LifeCycle_aboutToRecycle:", "组件即将进入复用缓存")
  }

  build() {
    Column() {
      Text(this.message)
        .fontSize(20)
    }
    .borderWidth(2)
    .height(100)
    .justifyContent(FlexAlign.Center)
  }
}

运行参考:

recording.gif

5. 总结

以上就是目前鸿蒙开发中比较常见的生命周期钩子啦。正确使用这些生命周期钩子是构建高效、可维护的前端组件的关键。
每个钩子都有其特定的用途,理解它们的使用场景和执行顺序,可以帮助我们更好地控制组件的行为和性能。
当然这里只对常用的生命周期进行了介绍,如果想了解更全面的生命周期方法,可以进入鸿蒙的官方开发文档进行查阅~