两种生命周期原理与介绍

  1. UIAbility组件生命周期。

    UIAbility组件是一种包含UI的应用组件,主要用于和用户交互。UIAbility组件是系统调度的基本单元,为应用提供绘制界面的窗口。一个应用可以包含一个或多个UIAbility组件。例如在支付应用中,可以将入口功能和收付款功能分别配置为独立的UIAbility。

    更多内容可查看:Stage模型应用组件UIAbility组件

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

    UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。WindowStage创建完成后会进入onWindowStageCreate()回调,可以在该回调中设置UI加载,设置WindowStage的事件订阅。

  2. 页面/组件生命周期

    Navigation生命周期

    针对一次操作同步销毁和创建同步创建的场景(如replace),遵循以下规则:

    • 涉及到页面的生命周期顺序aboutToAppear(外层自定义组件) -\> onWillAppear(进场Destination页面) -\> onWillHide(退出Destination页面) -\> onAppear(进场页面) -\> onWillShow(进场页面) -\> onHide(退出页面) -\> onWillDisAppear(退场页面) -\> onShow(进场页面) -\> onDisAppear(退场页面) -\> aboutToDisAppear(退场页面)。
    • 针对willShow和willHide生命周期有2种情形不会触发:前后台切换,router+Navigation混合使用,使用router跳转导致Navigation中的页面隐藏或者显示时不会触发willShow/willHide的流程。
    • Dialog场景触发规则遵循show相关的生命周期(onWillShow,onShow)均从栈底到栈顶依次触发,hide相关生命周期(onWillHide,onHide)均从栈顶到栈底依次触发。
    • 一次性清理多个页面,触发多个页面销毁(clear),对应的触发流程为栈顶页面最后销毁,堆栈中的其他页面从栈顶到栈底依次触发onHide -\> onWillDisAppear -\> onDisAppear。
    • clear1,2,3页面,组件生命周期执行顺序, 当前页面是3(1). 销毁除当前页面的其他页面:从栈顶向栈底依次触发onHide -\> onWillDisAppear -\> onDisAppear(2). 销毁当前页面:onHide -\> onWillDisAppear -\> onDisAppear。

    router

    自定义组件的生命周期回调函数用于通知用户该自定义组件的生命周期,这些回调函数是私有的,在运行时由开发框架在特定的时间进行调用,不能从应用程序中手动调用这些回调函数。

    可参考:生命周期定义

实际场景中生命周期的应用

场景一:启动框架的手动配置时机选择(性能优化)

场景描述以及生命周期钩子选择

场景:启动框架预加载SDK手动配置时选择。

生命周期:UIAbility生命周期的 onCreate() 回调。

方案说明

在UIAbility的onCreate生命周期回调中进行SDK或者任务初始化。

代码实现

export default class EntryAbility extends UIAbility {
    Logger.info('Ability onCreate')
    // 缓存应用状态: context
    AppStorage.setOrCreate('context', this.context)
    let startParams = ['StartupTask_005']
    try {
      startupManager.run(startParams).then(() => {
        Logger.info(`StartupTest startupManager run then, startParams = ${JSON.stringify(startParams)}`)
      }).catch((error: BusinessError) => {
        Logger.error(`StartupTest promise catch error, error = ${JSON.stringify(error)}; StartupTest promise catch error, startParams = ${JSON.stringify(startParams)}`)
      })
    } catch (error) {
      Logger.error(`Startup catch error , err = ${JSON.stringify(error)}`)
    }
  //...
}

场景二:应用状态初始化(定义全局变量等)

场景描述以及生命周期钩子选择

场景:

  1. 将应用状态 EntryAbility中的context和windowStage存储在AppStorage中,以便于其它业务模块使用。
  2. 注册全局字体。
  3. 初始化启动页,欢迎页,广告页。

生命周期:

  1. UIAbility生命周期的 onCreate()回调,
  2. UIAbility生命周期的 onWindowStageCreate()回调。

在onCreate设置 context上下文。在onWindowStageCreate回调中初始化页面,将windowStage储存到AppStorage,windowStage.loadContent加载页面。

核心代码

// 设置全局变量/注册自定义字体 
onWindowStageCreate(windowStage: window.WindowStage): void {
  Logger.info('Ability onWindowStageCreate')
  // 缓存应用状态: windowStage
  AppStorage.setOrCreate('windowStage', windowStage)
  // 获取应用权限
  reqPermissionsFromUser(permissions, this.context)
  // 启动页,广告页,欢迎页加载
  windowStage.loadContent('pages/Welcome/Welcome', (err) => {
    if (err.code) {
      Logger.error(`Failed to load the content, err = ${JSON.stringify(err)}`)
      return
    }
    // 注册全局字体
    FontRegister.registerCustomFont()
    Logger.info('Succeeded in loading the content')
  })
}
// 其他模块使用
import { common } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  context: common.UIAbilityContext | null = AppStorage.get('context') || null
  @StorageLink('windowStage') windowStage: window.WindowStage | null = AppStorage.get('windowStage') || null
  // ...
}

场景三:页面初始化

场景描述以及生命周期钩子选择

场景:

  1. Ajax数据请求。
  2. 权限获取以及地图定位功能。
  3. 定时器的开启与关闭。

生命周期:

  1. 页面生命周期的aboutToAppear(),aboutToDisappear()回调。
  2. 页面生命周期的onPageShow(),onPageHide()回调。

方案说明

  1. 在aboutToAppear生命周期回调 加载应用数据和授权。
  2. 在aboutToDisappear生命周期回调中关闭定时器。

代码实现

主页面关键代码:

import { common } from '@kit.AbilityKit';
import Logger from '../Utils/Logger'
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { getProjectList } from '../api/index'

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  context: common.UIAbilityContext | null = AppStorage.get('context') || null
  @StorageLink('windowStage') windowStage: window.WindowStage | null = AppStorage.get('windowStage') || null
  subWindowClass: window.Window | null = null;
  @State loading: boolean = false
  @State list: string[] = []
  private appCtx: common.Context | undefined
  private msgText: string = '我是消息'
  private msgId: number = 0

  aboutToAppear(): void {
    // 获取appContext 信息
    this.getAppCtx()
    // 获取应用数据
    this.initList()
  }

  getAppCtx() {
    try {
      this.appCtx = this.context?.getApplicationContext()
      Logger.info(`applicationContext : ${JSON.stringify(this.appCtx)}`)
    } catch (error) {
      Logger.error(`getApplicationContext failed, error.code: ${error.code}, error.message: ${error.message}`)
    }
    Logger.info(`windowStage : ${JSON.stringify(this.windowStage)}`)
  }

  onPageShow(): void {
    this.initList()
  }

  /* 获取业务数据 */
  async initList() {
    this.loading = true
    const res = await getProjectList()
    this.loading = false
    if (res.code === 200 && res.data) {
      this.list = res.data
    }
  }

  //...
}

子窗口页面关键代码:

import Util from '../Utils/Util'
import Logger from '../Utils/Logger'
import { MapComponent, mapCommon, map } from '@kit.MapKit'
import { AsyncCallback, BusinessError } from '@kit.BasicServicesKit'
import geoLocationManager from '@ohos.geoLocationManager'
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl'

@Entry
@Component
struct WindowPage {
  @State message: string = '子窗口';
  @State currentTime: string = Util.getCurrentTime()
  timer: number | null = null
  /* 地图相关 */
  TAG = "HuaweiMapDemo"
  mapOption?: mapCommon.MapOptions
  callback?: AsyncCallback<map.MapComponentController>
  mapController?: map.MapComponentController
  // 纬度
  @State lat: Number = -1
  //经度
  @State lng: Number = -1

  aboutToAppear(): void {
    // 应用授权
    this.requestLocationPermissions()
    // 初始化地图
    this.initMapOption()
  }

  onPageShow(): void {
    Logger.info(`onPageShow`)
    this.timer = setInterval(() => {
      this.currentTime = Util.getCurrentTime()
      Logger.info('当前时间: ', `${this.currentTime}`)
    }, 1000)
    this.getLocation()
    Logger.info(`开启定时器`, '')
  }

  onPageHide(): void {
    Logger.info(`onPageHide`)
  }

  aboutToDisappear(): void {
    Logger.info(`aboutToDisappear`)
    this.timer && clearInterval(this.timer)
    Logger.info(`页面销毁-关闭定时器: ${this.timer}`)
  }

  initMapOption() {
    // 地图初始化参数,设置地图中心点坐标及层级
    this.mapOption = {
      position: {
        target: {
          latitude: 39.9,
          longitude: 116.4
        },
        zoom: 10
      }
    }

    // 地图初始化的回调
    this.callback = async (err, mapController) => {
      if (!err) {
        // 获取地图的控制器类,用来操作地图
        this.mapController = mapController
        this.mapController.on("mapLoad", () => {
          console.info(this.TAG, `on-mapLoad`);
        })

        // 执行自定义的方法
        this.customizedMethod()
      }
    }
  }

  /* 获取当前经纬度 */
  getLocation() {
    Logger.info('获取定位权限')
    let requestInfo: geoLocationManager.CurrentLocationRequest = {
      priority: 0x203,
      scenario: geoLocationManager.LocationRequestScenario.DAILY_LIFE_SERVICE,
      maxAccuracy: 1000,
      timeoutMs: 5000
    }
    if (geoLocationManager.isLocationEnabled()) {
      Logger.info('A:定位已使能')
    } else {
      Logger.info('A:定位未使能')
    }
    try {
      geoLocationManager.getCurrentLocation(requestInfo, (err, location) => {
        if (err) {
          Logger.error(`定位失败,失败原因为:${err.message}`)
          return
        }
        console.info('位置获取成功')

        this.lat = location.latitude
        this.lng = location.longitude
        console.info('this.lat = ' + this.lat)
        console.info('this.lng = ' + this.lng)
      })
    } catch (err) {
      Logger.error(`报错原因为: ${err}`)
    }
  }

  private requestLocationPermissions() {
    const permissions: Array<Permissions> = ["ohos.permission.APPROXIMATELY_LOCATION", "ohos.permission.LOCATION"]
    let atManager = abilityAccessCtrl.createAtManager()
    // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
    atManager.requestPermissionsFromUser(getContext(this), permissions).then((data) => {
      let grantStatus: Array<number> = data.authResults
      let length: number = grantStatus.length
      for (let i = 0; i < length; i++) {
        if (grantStatus[i] === 0) {
          // 用户授权,可以继续访问目标操作
          return
        } else {
          // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
          return;
        }
      }
    }).catch((err: BusinessError) => {
      Logger.error(`requestPermissionsFromUser failed, code is ${err.code}, message is ${err.message}`)
    })
  }

  //...
}

场景四:状态监听,消息发布订阅

场景:某些场景下需要使用消息的发布订阅模式去实现业务功能,发布消息,订阅消息,改变页面。

生命周期:

  1. 页面生命周期的aboutToAppear(),aboutToDisappear()回调。
  2. 页面生命周期的 onPageShow(),onPageHide()回调。

当页面或者组件需要使用消息机制处理业务逻辑时,消息的发布订阅时机选择。aboutToAppear生命周期回调中订阅消息,aboutToDisappear生命周期回调中取消订阅。

代码实现

主页面关键代码:点击按钮发布消息:

import { common } from '@kit.AbilityKit';
import { router, window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { getProjectList } from '../api/Index';
import { emitter } from '@kit.BasicServicesKit';
import { MsgComp } from '../pages/components/CustomComp/CustomComp';
import Logger from '../Utils/Logger';

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  context: common.UIAbilityContext | null = AppStorage.get('context') || null;
  @StorageLink('windowStage') windowStage: window.WindowStage | null = AppStorage.get('windowStage') || null;
  subWindowClass: window.Window | null = null;
  @State loading: boolean = false;
  @State list: string[] = [];
  @State showComp: boolean = false;
  @State msgTimerId: number | undefined = undefined;
  private appCtx: common.Context | undefined;
  private msgText: string = '我是消息';
  private msgId: number = 0;

  changeCustomComp() {
    this.showComp = !this.showComp
    if (!this.showComp) {
      this.closeTimer()
    }
  }

  changeMsg() {
    if (!this.msgTimerId) {
      this.startMsg()
    } else {
      this.closeTimer()
    }
  }

  startMsg() {
    this.msgTimerId = setInterval(() => {
      this.msgId++
      const eventData: emitter.EventData = {
        data: {
          "content": this.msgText + this.msgId,
        }
      }
      Logger.info('发送消息:', JSON.stringify(eventData))
      emitter.emit('emitterMsg', eventData)
    }, 1000)
  }

  closeTimer() {
    this.msgTimerId && clearInterval(this.msgTimerId)
    this.msgTimerId = undefined
    this.msgId = 0
  }

  //...
}

子组件核心代码:订阅消息,取消订阅:

import { emitter } from '@kit.BasicServicesKit';
import Logger from '../../../Utils/Logger';
import type { EmitterEventData } from '../../../types/common.d';


@Component
export struct MsgComp {
  @State msg: string | undefined = '--'

  aboutToAppear(): void {
    // 订阅消息
    emitter.on('emitterMsg', (e: emitter.EventData) => {
      Logger.info(`收到消息:${JSON.stringify(e)}`)
      const data: EmitterEventData | undefined = e.data
      if (data) {
        this.msg = data.content
      }
    })
  }

  aboutToDisappear(): void {
    // 取消订阅
    emitter.off('emitterMsg')
    Logger.info('取消订阅')
  }

  build() {
    Column() {
      Row() {
        Text('我是自定义组件')
      }.width('100%').height('50%')

      Row() {
        Text(`这是我订阅的消息: ${this.msg}`).margin({
          top: 10
        })
      }.width('100%').height('50%')
    }
    .width('100%')
    .height(100)
    .border({
      width: 1,
    })
  }
}

场景五:NavDestination生命周期时序

针对 NavDestination 的使用以及生命周期回调,主要研究push,pop,replace,clear四种API与Navigation生命周期的关系。

Navigation的push场景

  1. 触发API与调用时机

    NavPathStack.pushPath()

  2. 效果展示与执行结果:

Navigation的pop场景

  1. 触发API与调用时机

    NavPathStack.pop()

  2. 效果展示与执行结果:

Navigation的replace场景

触发API与调用时机

NavPathStack.replacePath()

调用方式:pageOne使用replacePath跳转到pageTwo。

页面1与页面2的生命周期执行顺序:

本场景示例代码:

// 该示例演示NavDestination的生命周期时序。

// 该示例演示NavDestination的生命周期时序。
import { router } from '@kit.ArkUI'
import Logger from '../../Utils/Logger'

@Builder
export function pagesMap(name: string, param: Object) {
  if (name === 'page1') {
    Page1()
  } else if (name === 'page2') {
    Page2()
  } else if (name === 'page3') {
    Page3()
  }
}

@Component
struct Page1 {
  @State eventStr: string = ''
  private stack: NavPathStack | null = null
  build() {
    NavDestination() {
      Column() {
        Text('event: ' + this.eventStr)
        Button('pushPath', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            if (this.stack) {
              this.stack.pushPath({ name: 'page2' })
            }
          })
        Button('pop', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.stack?.pop()
          })

        Button('replace', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.stack?.replacePath({ name: 'page2' })
          })

        Button('clear', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.stack?.clear()
          })
      }
      .width('100%')
      .height('100%')
    }
    .title('page1')
    .onAppear(() => {
      this.eventStr += '<onAppear>'
      Logger.info('NavDestination-life-page1: onAppear')
    })
    .onDisAppear(() => {
      this.eventStr += '<onDisAppear>'
      Logger.info('NavDestination-life-page1: onDisAppear')
    })
    .onShown(() => {
      this.eventStr += '<onShown>'
      Logger.info('NavDestination-life-page1: onShown')
      if (this.stack) {
        Logger.info(`页面栈长度: ${this.stack.size().toString()}`)
      }
    })
    .onHidden(() => {
      this.eventStr += '<onHidden>'
      Logger.info('NavDestination-life-page1: onHidden')
    })
    // onReady会在onAppear之前调用
    .onReady((ctx: NavDestinationContext) => {
      try {
        this.eventStr += '<onReady>'
        this.stack = ctx.pathStack
        Logger.info('NavDestination-life-page1: onReady')
      } catch (e) {
        Logger.info(`testTag onReady catch exception: ${JSON.stringify(e)}`)
      }
    })
  }
}

@Component
struct Page2 {
  @State eventStr: string = ''
  private stack: NavPathStack | null = null
  build() {
    NavDestination() {
      Column() {
        Text('event: ' + this.eventStr)
        Button('pushPath', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            if (this.stack) {
              this.stack.pushPath({ name: 'page3' })
            }
          })
      }
      .width('100%')
      .height('100%')
    }
    .title('page2')
    .onAppear(() => {
      this.eventStr += '<onAppear>'
      Logger.info('NavDestination-life-page2: onAppear')
    })
    .onDisAppear(() => {
      this.eventStr += '<onDisAppear>'
      Logger.info('NavDestination-life-page2: onDisAppear')
    })
    .onShown(() => {
      this.eventStr += '<onShown>'
      Logger.info('NavDestination-life-page2: onShown')
      if (this.stack) {
        Logger.info(`页面栈长度: ${this.stack.size().toString()}`)
      }
    })
    .onHidden(() => {
      this.eventStr += '<onHidden>'
      Logger.info('NavDestination-life-page2: onHidden')
    })
    // onReady会在onAppear之前调用
    .onReady((ctx: NavDestinationContext) => {
      try {
        this.eventStr += '<onReady>'
        this.stack = ctx.pathStack
        Logger.info('NavDestination-life-page2: onReady')
      } catch (e) {
        Logger.info(`testTag onReady catch exception: ${JSON.stringify(e)}`)
      }
    })
  }
}

@Component
struct Page3 {
  @State eventStr: string = ''
  private stack: NavPathStack | null = null
  build() {
    NavDestination() {
      Column() {
        Text('event: ' + this.eventStr)
        Button('replacePath', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            if (this.stack) {
              this.stack.replacePath({ name: 'page2' })
            }
          })

        Button('pushPath', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            if (this.stack) {
              this.stack.replacePath({ name: 'page1' })
            }
          })

        Button('to Index', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            router.pushUrl({
              url: 'pages/NavPage/NavPage'
            })
          })
        Button('clear', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            if (this.stack) {
              console.log('点击clear')
              this.stack.clear()
            }
          })
      }
      .width('100%')
      .height('100%')
    }
    .title('page3')
    .onAppear(() => {
      this.eventStr += '<onAppear>'
      console.log(`NavDestination-life-page3: onAppear`)
    })
    .onDisAppear(() => {
      this.eventStr += '<onDisAppear>'
      console.log(`NavDestination-life-page3: onDisAppear`)
    })
    .onShown(() => {
      this.eventStr += '<onShown>'
      console.log(`NavDestination-life-page3: onShown`)
      if (this.stack) {
        console.log(`页面栈长度: ${this.stack.size().toString()}`)
      }

    })
    .onHidden(() => {
      this.eventStr += '<onHidden>'
      console.log(`NavDestination-life-page3: onHidden`)
    })
    // onReady会在onAppear之前调用
    .onReady((ctx: NavDestinationContext) => {
      try {
        this.eventStr += '<onReady>'
        this.stack = ctx.pathStack
        console.log(`NavDestination-life-page3: onReady`)

      } catch (e) {
        console.log(`testTag onReady catch exception: ${JSON.stringify(e)}`)
      }
    })
  }
}

@Entry
@Component
struct NavPage {
  private stack: NavPathStack = new NavPathStack()

  build() {
    Navigation(this.stack) {
      Column() {
        Button('pushPath', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.stack.pushPath({ name: 'page1' })
          })

        Button('clear', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.stack?.clear()
          })
      }
      .width('100%')
      .height('100%')
    }
    .width('100%')
    .height('100%')
    .title('Navigation')
    .navDestination(pagesMap)
  }
}

HarmonyOS码上奇行
7k 声望2.8k 粉丝