@[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()
工具类使用示例
- 首先我们需要在
EntryAbility
类中,初始化这个工具类
typescript
onWindowStageCreate(windowStage: window.WindowStage): void {
// 初始化工具类
windowStage.getMainWindow().then((window) => {
// 初始化生命周期的对象方法
lifecycle.init(window.getUIContext())
})
}
- 在页面中的应用
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