HarmonyOS - UIObserver(无感监听)

@[TOC](HarmonyOS - UIObserver(无感监听))

概述

本篇文档主要介绍,在鸿蒙项目开发过程中,我们如何使用UIObserver对象提供的对UI组件行为变化的无感监听能力。

基于当前鸿蒙项目, 我们是把页面所有的数据都存放在一个叫UIState对象中, 并且ViewModel持有这个UIState对象,而且ViewModel中包含了所有业务的逻辑。

当我们在ViewModel中请求数据时,需要知道在什么时机去请求, 那么我们就需要再ViewModel中知道View的一些生命周期方法的变化。

使用鸿蒙提供的UIObserver即可实现该效果。

UIObserver无感监听Router

监听代码:

typescript 复制代码
// 获取UIObserver对象
const lifecycleObserver: UIObserver = uiContext.getUIObserver()
lifecycleObserver.on('routerPageUpdate', (routerPageInfo: RouterPageInfo) => {
      console.log(`[lifecycle] routerPageUpdate============: ${this.stringify(routerPageInfo)}`)
    })

通过Router的方式push一个PageThree页面, 输出结果如下:

我们可以看到在页面状态发生变化时,注册的回调将会触发,开发者可以通过回调中传入的入参拿到页面的相关信息,如:页面的名字,索引,路径,生命周期状态等

回调方法的RouterPageInfo对象包含了上述的所有信息

UIObserver无感监听Navigation

示例代码:

typescript 复制代码
const lifecycleObserver: UIObserver = uiContext.getUIObserver()

// 监听navDestination组件 切换到另外一个navDestination组件的变化
lifecycleObserver.on('navDestinationSwitch', (navDestinationSwitchInfo: uiObserver.NavDestinationSwitchInfo) => {
      console.log(`[lifecycle] navDestinationSwitch==================: ${this.stringify(navDestinationSwitchInfo)}`)
    })

// 监听navDestination组件(页面)生命周期发生变化
 lifecycleObserver.on('navDestinationUpdate', (navDestinationInfo: NavDestinationInfo) => {

      console.log(`[lifecycle] navDestinationUpdate=====================: ${this.stringify(navDestinationInfo)}`)
 })

输出结果:

根据结果可以知道, 通过上面的两个方法的监听, 我们可以知道页面的来源,当前页面的生命周期变化等信息。

UIObserver无感监听Tab

typescript 复制代码
// 获取UIObserver对象
const lifecycleObserver: UIObserver = uiContext.getUIObserver()
// 注册tabContentUpdate的监听, 当tabContent组件变化时会回调
lifecycleObserver.on('tabContentUpdate', (tabContentInfo: uiObserver.TabContentInfo) => {
      console.log(`[lifecycle] tabContentUpdate====================: ${this.stringify(tabContentInfo)}`)

    })

测试结果:

其实关于UIObserver, 这里只介绍了比较常用的三种,官网文档中其实还有很多无感监听其他事件的api, 如果想知道更多请参考官方文档UIObsever官方文档

UIObserver的工具类封装

工具类示例代码

typescript 复制代码
import { ArrayList } from '@kit.ArkTS'
import { router, uiObserver, UIObserver } from '@kit.ArkUI'

export interface ILifecycleId {
  tabId?: string
  pageId?: string
  navDestinationId?: string
}

export function getLifecycleId(component: CustomComponent, tabId?: string): ILifecycleId {
  return {
    tabId: tabId,
    pageId: component.queryRouterPageInfo()?.pageId,
    navDestinationId: component.queryNavDestinationInfo()?.navDestinationId,
  }
}

/**
 * 定义页面的个个生命周期的枚举, 具体的值参考个个监听回调方法中的状态值
 */
export enum LifecycleState {
  onCreate = 'onCreate', // 页面创建的时候
  onVisible = 'onVisible', // 页面显示
  onInvisible = 'onInvisible', // 页面隐藏
  onDestroy = 'onDestroy', // 页面

  onTabSelect = 'onTabSelect', // TabContent 组件显示(选中)
  onTabUnselect = 'onTabUnselect', // TabContent组件隐藏(未选中)
}

// 定义一个生命周期状态变量
export type TabLifeState = LifecycleState.onVisible | LifecycleState.onInvisible | LifecycleState.onTabSelect
  | LifecycleState.onTabUnselect


/**
 * 定义一个页面信息对象
 */
export interface ILifecycleObserver {
  tabId?: string
  pageId?: string
  navDestinationId?: string
  uiAbility?: boolean
  onReceive: (state: LifecycleState) => void
}

export class Lifecycle {
  /** 缓存监听的对象列表 */
  private observerList: ArrayList<ILifecycleObserver> = new ArrayList()
  /** 缓存Router下监听的列表 */
  private routerPageInfoMap: Map<string, uiObserver.RouterPageInfo> = new Map()
  /** 缓存navDestination监听的列表 */
  private navDestinationStateMap: Map<string, uiObserver.NavDestinationState> = new Map()

  // private mainTabSelectProvider?: IMainTabSelectProvider
  // private uiAbilityLifecycleProvider?: IUIAbilityLifecycleProvider

  init(uiContext: UIContext) {

    const lifecycleObserver: UIObserver = uiContext.getUIObserver()

    // 无感监听router页面变化回调方法
    lifecycleObserver.on('routerPageUpdate', (routerPageInfo: RouterPageInfo) => {
      console.log(`[lifecycle] routerPageUpdate============: ${this.stringify(routerPageInfo)}`)
      // 把Router栈中的页面都缓存到map中
      this.routerPageInfoMap.set(routerPageInfo.pageId, routerPageInfo)
      // 处理Router个个page的生命周期回调
      this.handlePageState(routerPageInfo.pageId, routerPageInfo.state)
      // 移除已经销毁页面的监听
      this.checkRemoveIfOnPageDestroy(routerPageInfo)

    })

    lifecycleObserver.on('tabContentUpdate', (tabContentInfo: uiObserver.TabContentInfo) => {
      console.log(`[lifecycle] tabContentUpdate====================: ${this.stringify(tabContentInfo)}`)
      // 该方法可以处理
    })

    // 监听Navigation的页面切换事件。
    lifecycleObserver.on('navDestinationSwitch', (navDestinationSwitchInfo: uiObserver.NavDestinationSwitchInfo) => {
      console.log(`[lifecycle] navDestinationSwitch==================: ${this.stringify(navDestinationSwitchInfo)}`)

      // 如果我们整个项目是直接使用Navigation, 我们可以在这个监听方法内部, 判断是否回到主页面

      let mainPageState: LifecycleState | undefined
      // 判断是否从主界面跳转来的
      if(navDestinationSwitchInfo.from === 'navBar') {
        mainPageState = LifecycleState.onInvisible
      } else if(navDestinationSwitchInfo.to === 'navBar') {
        // 判断是否从其他页面回到主页面
        mainPageState = LifecycleState.onVisible
      }

      if(!mainPageState) {
        return
      }

      // 如果你是采用Router + Navigation 实现页面跳转, 可如下判断是否回到主页 (例如: 登录,欢迎页使用router, 主页使用Navigation)
      const mainRoutePageInfoFound = Array.from(this.routerPageInfoMap.values()).find((routePageInfo) => routePageInfo.name === 'main' )
      if(!mainRoutePageInfoFound) {
        return
      }

      // 处理回到主页事件
      this.dispatchMainPageEvent(mainRoutePageInfoFound.pageId, mainPageState)

    })
    // 监听NavDestination组件的状态变化。  生命周期方法的变化
    lifecycleObserver.on('navDestinationUpdate', (navDestinationInfo: NavDestinationInfo) => {
      console.log(`[lifecycle] navDestinationUpdate=====================: ${this.stringify(navDestinationInfo)}`)

      // 缓存每个NavDestination路由的状态
      this.navDestinationStateMap.set(navDestinationInfo.navDestinationId, navDestinationInfo.state)
      // 处理NavDestination的状态
      this.handleNavDestinationState(navDestinationInfo.navDestinationId, navDestinationInfo.state)

      // 处理销毁的NavDestination
      this.checkRemoveIfOnNavDestinationDestroy(navDestinationInfo)
    })
  }

  private stringify(value: object) {
    const body = JSON.stringify(value, (_, v: object) => {
      const value: string | object = typeof v === 'bigint' ? (v as bigint).toString() : v
      return value
    })

    return body
  }

  private checkRemoveIfOnNavDestinationDestroy(navDestinationInfo: uiObserver.NavDestinationInfo) {
    if (navDestinationInfo.state === uiObserver.NavDestinationState.ON_DISAPPEAR) {
      const observerMatch = this.observerList.convertToArray()
        .find((observer) => observer.navDestinationId === navDestinationInfo.navDestinationId)
      if (observerMatch) {
        this.removeObserver(observerMatch)
      }
    }
  }


  /**
   * 当前页面销毁时,我们需要删除该页面的监听
   * @param routerPageInfo
   */
  private checkRemoveIfOnPageDestroy(routerPageInfo: uiObserver.RouterPageInfo) {
    if (routerPageInfo.state === uiObserver.RouterPageState.ABOUT_TO_DISAPPEAR) {
      const observerMatch =
        this.observerList.convertToArray().find((observer) => observer.pageId === routerPageInfo.pageId)
      if (observerMatch) {
        this.removeObserver(observerMatch)
      }
    }
  }

  /**
   * 处理Router 中page的State方法
   * @param pageId 页面id
   * @param pageState 页面当前的生命周期状态
   */
  private handlePageState(pageId: string, pageState: uiObserver.RouterPageState) {
    switch (pageState) {
      case uiObserver.RouterPageState.ABOUT_TO_APPEAR:

        this.dispatchPageEvent(pageId, LifecycleState.onCreate)

        break


      case uiObserver.RouterPageState.ON_PAGE_SHOW:
        this.dispatchPageEvent(pageId, LifecycleState.onVisible)
        break

      case uiObserver.RouterPageState.ON_PAGE_HIDE:
        this.dispatchPageEvent(pageId, LifecycleState.onInvisible)
        break


      case uiObserver.RouterPageState.ABOUT_TO_DISAPPEAR:
        this.dispatchPageEvent(pageId, LifecycleState.onDestroy)

        break

    }
  }


  private handleNavDestinationState(navDestinationId: string, pageState: uiObserver.NavDestinationState) {
    switch (pageState) {
      case uiObserver.NavDestinationState.ON_APPEAR:
        this.dispatchNavDestinationEvent(navDestinationId, LifecycleState.onCreate)
        break

      case uiObserver.NavDestinationState.ON_SHOWN:
        this.dispatchNavDestinationEvent(navDestinationId, LifecycleState.onVisible)
        break

      case uiObserver.NavDestinationState.ON_HIDDEN:
        this.dispatchNavDestinationEvent(navDestinationId, LifecycleState.onInvisible)
        break

      case uiObserver.NavDestinationState.ON_DISAPPEAR:
        this.dispatchNavDestinationEvent(navDestinationId, LifecycleState.onDestroy)
        break

    }
  }

  private dispatchMainPageEvent(pageId: string, state: LifecycleState) {
    this.observerList
      .convertToArray()
      .filter((observer) => observer.pageId === pageId)
      .forEach((observer: ILifecycleObserver) => {
        this.doDispatchPageEvent(observer, state)
      })
  }


  /**
   * 处理NavDestination发生变化时的回调
   * @param navDestinationId  NavDestination组件的id
   * @param state 生命周期状态
   */
  private dispatchNavDestinationEvent(navDestinationId: string, state: LifecycleState) {
    this.observerList
      .convertToArray()
      .filter((observer) => observer.navDestinationId === navDestinationId)
      .forEach((observer: ILifecycleObserver) => {
        observer.onReceive(state)
      })
  }

  /**
   * 处理Router中页面的变化时的回调
   * @param pageId router page的id
   * @param state 生命周期状态
   */
  private dispatchPageEvent(pageId: string, state: LifecycleState) {
    this.observerList
      .convertToArray()
      .filter((observer) => observer.pageId === pageId)
      .forEach((observer: ILifecycleObserver) => {
        observer.onReceive(state)
      })
  }

  private doDispatchPageEvent(observer: ILifecycleObserver, state: LifecycleState) {
    if (observer.navDestinationId) {
      return
    }

    if (!observer.tabId) {
      observer.onReceive(state)
      return
    }
    observer.onReceive(state)
  }


  private dispatchUIAbilityEvent(state: LifecycleState) {
    this.observerList
      .convertToArray()
      .filter((observer) => observer.uiAbility === true)
      .forEach((observer: ILifecycleObserver) => {
        // logger.debug(`[lifecycle][uiAbility] onReceive ${state}`)
        observer.onReceive(state)

      })
  }


  selectTab(tabId: string) {
    this.dispatchTabEvent(tabId, LifecycleState.onTabSelect)
    this.dispatchTabEvent(tabId, LifecycleState.onVisible)
  }

  unselectTab(tabId: string) {
    this.dispatchTabEvent(tabId, LifecycleState.onTabUnselect)
    this.dispatchTabEvent(tabId, LifecycleState.onInvisible)
  }

  private dispatchTabEvent(tabId: string, state: TabLifeState) {
    this.observerList
      .convertToArray()
      .filter((observer) => observer.tabId === tabId)
      .forEach((observer: ILifecycleObserver) => {
        this.doDispatchTabEvent(observer, state)
      })
  }

  private doDispatchTabEvent(observer: ILifecycleObserver, state: TabLifeState) {
    if (!observer.pageId) {
      observer.onReceive(state)
      return
    }

    const pageInfo = this.routerPageInfoMap.get(observer.pageId)
    if (pageInfo?.state === uiObserver.RouterPageState.ON_PAGE_SHOW) {
      observer.onReceive(state)
    }

  }


  // 增加一个页面监听
  addObserver(observer: ILifecycleObserver, sticky: boolean = true) {
    this.observerList.add(observer)

    if (sticky) {
      this.observerUICallbackSticky(observer)
    }
  }

  private observerUICallbackSticky(observer: ILifecycleObserver) {
    const lifecycleState = this.fetchLifecycleState(observer)

    if (!lifecycleState) {
      return
    }

    if (lifecycleState === LifecycleState.onDestroy) {
      observer.onReceive(LifecycleState.onInvisible)
      observer.onReceive(LifecycleState.onDestroy)
      return
    }


    if (!observer.tabId) {
      observer.onReceive(LifecycleState.onCreate)

      if (lifecycleState !== LifecycleState.onCreate) {

        if (observer.navDestinationId) {
          this.dispatchNavDestinationEvent(observer.navDestinationId, lifecycleState)
        } else if (observer.pageId) {
          this.doDispatchPageEvent(observer, lifecycleState)
        }


      }
      return
    }
  }


  private fetchLifecycleState(observer: ILifecycleObserver): LifecycleState | undefined {
    let lifecycleState: LifecycleState | undefined
    const pageId = observer.pageId
    const navDestinationId = observer.navDestinationId
    if (pageId) {
      const pageInfo = this.routerPageInfoMap.get(pageId)
      lifecycleState = pageInfo ? transformLifecycleStateFromPage(pageInfo.state) : undefined

    } else if (navDestinationId) {
      const destinationState = this.navDestinationStateMap.get(navDestinationId)
      lifecycleState = destinationState ? transformLifecycleStateFromNavDestination(destinationState) : undefined
    }
    return lifecycleState
  }

  /**
   * 移除一个页面监听
   * @param observer
   */
  removeObserver(observer: ILifecycleObserver) {
    this.observerList.remove(observer)
  }


  release() {
    this.observerList.clear()
  }
}

function transformLifecycleStateFromPage(pageState: uiObserver.RouterPageState): LifecycleState | undefined {
  switch (pageState) {
    case uiObserver.RouterPageState.ABOUT_TO_APPEAR:
      return LifecycleState.onCreate

    case uiObserver.RouterPageState.ABOUT_TO_DISAPPEAR:
      return LifecycleState.onDestroy

    case uiObserver.RouterPageState.ON_PAGE_SHOW:
      return LifecycleState.onVisible

    case uiObserver.RouterPageState.ON_PAGE_HIDE:
      return LifecycleState.onInvisible

    default:
      return undefined

  }

}


function transformLifecycleStateFromNavDestination(navDestinationState: uiObserver.NavDestinationState): LifecycleState | undefined {
  switch (navDestinationState) {
    case uiObserver.NavDestinationState.ON_APPEAR:
      return LifecycleState.onCreate

    case uiObserver.NavDestinationState.ON_DISAPPEAR:
      return LifecycleState.onDestroy

    case uiObserver.NavDestinationState.ON_SHOWN:
      return LifecycleState.onVisible

    case uiObserver.NavDestinationState.ON_HIDDEN:
      return LifecycleState.onInvisible

    default:
      return undefined

  }

}


export const lifecycle = new Lifecycle()

工具类使用示例

  1. 首先我们需要在EntryAbility类中,初始化这个工具类
typescript 复制代码
onWindowStageCreate(windowStage: window.WindowStage): void {

// 初始化工具类
    windowStage.getMainWindow().then((window) => {

      // 初始化生命周期的对象方法
      lifecycle.init(window.getUIContext())
    })

  }
  1. 在页面中的应用
typescript 复制代码
//TestPage.ets
import { ILifecycleId } from "./Lifecycle"
import { TestView } from "./TestView"

@Component
export struct TestPage {
  build() {
    NavDestination() {
      TestView({
        onReady: (lifecycleId: ILifecycleId) => {
          // 调用逻辑层的生命周期执行方法, 比如viewmodel中的监听方法
        }
      })
    }
  }
}

// TestView.ets
import { getLifecycleId, ILifecycleId } from './Lifecycle'

@Component
export struct TestView {
  onReady: (lifecycleId: ILifecycleId) => void = () => {}

  aboutToAppear(): void {
    this.onReady(getLifecycleId(this))
  }
  build() {
    Column() {

    }
  }
}

注意:getLifecycleId(this)这个句代码是能直接放在TestPage的aboutToAppear生命周期方法中的, 因为此时的NavDestination并没有创建,所以获取的navDestinationId都是undefined

相关推荐
二流小码农3 小时前
鸿蒙开发:绘制服务卡片
android·ios·harmonyos
libo_20254 小时前
HarmonyOS5 隐私标签验证:用静态扫描确保元服务声明权限与实际匹配
harmonyos
别说我什么都不会5 小时前
【OpenHarmony】 鸿蒙网络请求库之ohos_ntp
网络协议·harmonyos
很萌很帅的恶魔神ww6 小时前
HarmonyOS Next 之-组件之弹窗
harmonyos
很萌很帅的恶魔神ww6 小时前
HarmonyOS Next 底部 Tab 栏组件开发实战
harmonyos
云_杰6 小时前
HarmonyOS ——Telephony Kit(蜂窝通信服务)教程
harmonyos
很萌很帅的恶魔神ww6 小时前
HarmonyOS Next 之轮播图开发指南(Swiper组件)
harmonyos
别说我什么都不会8 小时前
【OpenHarmony】 鸿蒙网络请求库之eventsource
harmonyos
颜颜颜yan_10 小时前
【HarmonyOS5】掌握UIAbility启动模式:Singleton、Specified、Multiton
后端·架构·harmonyos
二蛋和他的大花11 小时前
HarmonyOS运动开发:深度解析文件预览的正确姿势
华为·harmonyos