基于无感监听实现全局页面埋点

FengTianYa
发布于 2025-1-22 11:31
浏览
0收藏

场景描述

当前许多应用都需要实现全局页面埋点能力,例如输出用户使用的页面路径、用户在每个页面的停留时间等,但是navigation路由场景会存在首页为page的情况,这时候使用navDestinationUpdate无法监听到首页的变化,想要在HarmonyOS上实现一个监听navigation所有页面的方法。

此demo暂不支持navigation嵌套场景,复杂场景推荐使用​​HMRouter​​。

显示效果

使用navgation路由:

1.首页pushPath到pageOne

2.pushPath到pageTwo

3.remove PageOne

4.返回首页

5.退出应用

基于无感监听实现全局页面埋点-鸿蒙开发者社区

基于无感监听实现全局页面埋点-鸿蒙开发者社区

基于无感监听实现全局页面埋点-鸿蒙开发者社区

使用router路由:

1.Index push到page1

2.replaceUrl到page3

3.返回Index

4.锁屏

基于无感监听实现全局页面埋点-鸿蒙开发者社区

基于无感监听实现全局页面埋点-鸿蒙开发者社区

核心代码

1.自定义页面对象,统一routerInfo和navInfo,保证每个页面的唯一性。

class PageInfo {
  //PageID或NavDestinationID
  id: string = ''
  name: ResourceStr = ''
  //navigationID或'RouterPage'
  uniqueId: string = ''
}

//统一navDestinationInfo与RouterPageInfo
function routerInfo(info: RouterPageInfo): PageInfo {
  let pageinfo = new PageInfo()
  pageinfo.id = info.pageId
  pageinfo.name = info.name
  pageinfo.uniqueId = 'RouterPage'
  return pageinfo

}

function navInfo(info: NavDestinationInfo): PageInfo {
  let pageinfo = new PageInfo()
  pageinfo.id = info.navDestinationId
  pageinfo.name = info.name
  pageinfo.uniqueId = info.navigationId as string
  return pageinfo
}

2.定义一个方法来监听页面。

stack用来存页面栈,其中页面信息的顺序与页面栈一致allInfo存页面以及对应的开始展示的时间。

export function observerAll(context: UIContext) {
  // key用info.id + info.name + info.navigationId做拼接,保证唯一性,value为开始展示的时间点
  let allInfo: HashMap<string, number> = new HashMap();
  // 存栈信息
  let stack: ArrayList<PageInfo> = new ArrayList()
  ......
}

3.声明全局的页面栈实例,用于缓存不同页面的状态和区分不同窗口的页面。

(1)navDestinationUpdate监听。

注意:navDestinationUpdate此监听是监听navDestination组件的生命周期变化,若存在单独使用navDestination组件的场景(不推荐),或者子窗的场景,这里请绑定NavigationID:

observer.on('navDestinationUpdate', { navigationId: "testId" }, (info) => {
  ......
});

onAppear:将页面信息存入栈中,打印栈中页面路径。

onDisappear:页面退栈,删除页面信息,并打印当前栈中的页面路径。

onShown:将onShown页面移入栈顶,并更新开始展示时间。

onHidden:计算页面展示时间。

observer.on('navDestinationUpdate', (info) => {
  let nPageInfo = navInfo(info);
  let allString = infoSplice(nPageInfo);
  //onAppear入栈
  if (info.state == 2) {
    stack.add(nPageInfo);
    //当前时间就是开始展示时间
    allInfo.set(allString, Date.now());
    let stackInfo = ''
    for (let i = 0; i < stack.length; i++) {
      let pageName = (stack[i] as PageInfo).name;
      stackInfo += pageName + ' -> ';
    }
    console.log('当前页面栈:' + stackInfo);
  }

  //onDisappear出栈,删除栈中信息,并打印之后的栈
  if (info.state == 3) {
    allInfo.remove(allString);
    let stackInfo = ''
    for (let i = 0; i < stack.length; i++) {
      if (isObjectChanged(stack[i], nPageInfo)) {
        stack.removeByIndex(i);
      }
    }
    for (let i = 0; i < stack.length; i++) {
      let pageName = (stack[i] as PageInfo).name;
      stackInfo += pageName + ' -> ';
    }
    console.log('当前页面栈:' + stackInfo);
  }
  //onShown更新页面出现时间,更新栈中顺序
  if (info.state == 0) {
    allInfo.set(allString, Date.now());
  }

  //onHidden计算页面展示时间
  if (info.state == 1) {
    let stateTime = Date.now() - allInfo.get(allString);
    console.log(nPageInfo.name + '显示时间为:' + stateTime);
  }
})
(2) navDestinationSwitch监听。

navDestinationUpdate监听无法监听到首页page的变化,navDestinationSwitch可以监听到首页page的变化,首页信息为NavBar

这里只需要补充跳转NavBar以及返回NavBar的场景:

observer.on('navDestinationSwitch', context, (info) => {

  //navBar->navDestination,需要打印page的出现时间
  if (info.from == "navBar") {
    let rPageInfo = stack[0] as PageInfo;
    let allString = infoSplice(rPageInfo);
    let stateTime = Date.now() - allInfo.get(allString);
    console.log(rPageInfo.name + '显示时间为:' + stateTime);
  }

  //navDestination返回根页面navBar
  if (info.to == "navBar") {
    let rPageInfo = stack[0] as PageInfo;
    let allString = infoSplice(rPageInfo);
    //更新根页面navBar出现时间
    allInfo.set(allString, Date.now());
  }
})
(3) routerPageUpdate监听。

特别说明:navDestinationUpdate与navDestinationSwitch监听无法兼顾到应用后台回到前台以及锁屏解锁的场景(若此时栈中存在navBar+navDestination,同时会触发navBar以及navDestination两个页面的回调),这时候就需要用routerPageUpdate补充,并增加监听使用router路由场景的功能。

大部分逻辑与navDestinationUpdate监听相同,但是在onPageHide展示时间前需要判断此页面是否为栈中最后一个,若为最后一个,则打印。

AboutToAppear:page入栈,打印此时栈中页面信息。

AboutToDisappear:页面出栈,删除页面信息,并打印当前栈中的页面路径。

onPageShow:将onPageShow页面移入栈顶,并更新开始展示时间。

onPageHide:计算页面展示时间。

//监听page生命周期变化
observer.on('routerPageUpdate', context, (info) => {
  let rPageInfo = routerInfo(info);
  let allString = infoSplice(rPageInfo);

  //aboutToAppear入栈,打印栈信息
  if (info.state == 0) {
    stack.add(rPageInfo);
    //当前时间就是开始展示时间
    allInfo.set(allString, Date.now());

    let stackInfo = ''
    for (let i = 0; i < stack.length; i++) {
      let pageName = (stack[i] as PageInfo).name;
      stackInfo += pageName + ' -> ';
    }
    console.log('当前页面栈:' + stackInfo);
  }

  //aboutToDisappear出栈,删除栈中信息,并打印栈信息
  if (info.state == 1) {
    allInfo.remove(allString)
    let stackInfo = '';
    for (let i = 0; i < stack.length; i++) {
      if (isObjectChanged(stack[i], rPageInfo)) {
        stack.removeByIndex(i);
      }
    }
    for (let i = 0; i < stack.length; i++) {
      let pageName = (stack[i] as PageInfo).name
      stackInfo += pageName + ' -> ';
    }
    console.log('当前页面栈:' + stackInfo);
  }

  //onPageShow更新页面出现时间
  if (info.state === 2) {
    allInfo.set(allString, Date.now());
  }

  //onPageHide计算页面展示时间
  if (info.state === 3) {
    //需要判断此page后是否存在navDestination,若存在,则不打印
    for (let i = 0; i < stack.length; i++) {
      if (isObjectChanged(stack[i], rPageInfo)) {
        if (i == stack.length - 1 || (stack[i+1] as PageInfo).uniqueId == 'RouterPage') {
          let stateTime = Date.now() - allInfo.get(allString);
          console.log(rPageInfo.name + '显示时间为:' + stateTime);
        }
      }
    }
  }
})

4.在主窗口创建后开启页面监听(推荐在ability.onWindowStageCreate\(\)中进行)。

onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/Index', (err) => {
    // 开始监听(不能早于该时机)
    observerAll(uiContext)
  });
}

5.在子窗口创建后开启页面监听。

windowStage.createSubWindow('subWindow').then(subWin=>{
  subWin.setUIContent('pages/Index').then((err)=>{
    // 开始监听(不能早于该时机)
  })
})

常见问题

Q1:如果router与navigation混用能监听到吗

A1:可以,但是更推荐使用navigation作为路由框架,如果使用navigation路由,一个Page+多个NavDestination即可满足路由需求。

Q2:如果一个应用内有多个窗口,对应的页面栈该是否共用?

A2:可以区分开,每个窗口都有自己的页面栈信息。

Q3:navigation嵌套场景能使用吗?

A3:复杂场景推荐使用​​HMRouter​​。

分类
收藏
回复
举报
回复
    相关推荐