HarmonyOS 鸿蒙Next基于UI Observer实现UI组件埋点

发布于 1周前 作者 htzhanglong 来自 鸿蒙OS

HarmonyOS 鸿蒙Next基于UI Observer实现UI组件埋点
找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

关于HarmonyOS 鸿蒙Next基于UI Observer实现UI组件埋点的问题,您也可以访问:https://www.itying.com/category-93-b0.html 联系官网客服。

10 回复

<markdown _ngcontent-uqj-c237="" class="markdownPreContainer">

组件埋点基本介绍

组件埋点是一种在前端开发中常用的数据采集方法,它通过在页面中的组件(例如按钮、输入框、下拉框等)上添加跟踪代码,来收集用户的操作行为数据。这些数据可以帮助开发人员分析用户的使用习惯、行为路径等信息,从而优化产品的设计和功能。

组件埋点的实现方式通常包括以下几个步骤:

  1. 确定需要收集的数据类型和指标,例如点击次数、输入内容、页面停留时间等。
  2. 在页面中的组件上添加跟踪代码,通常是在组件的事件处理函数中调用一个统一的数据采集函数。
  3. 将采集到的数据发送到后端服务器进行存储和分析。
  4. 对采集到的数据进行分析和处理,以便于开发人员进行产品优化和改进。

本文主要介绍如何在鸿蒙中实现埋点数据的收集,上报展示数据方式为反显到屏幕中,如下图所示,可根据实际进行调整

常见场景实现

按钮点击:全局监听用户在页面中触发的点击事件。

点击事件分为手势事件和click事件,通过UIObserver.on('willClick')方法注册点击事件和手势事件监听,后续页面中所有的onClick与gesture都可以监听到,这个监听在同一个UIContext下是全局生效的;在用户触发点击操作后,会回调返回当前点击元素的FrameNode对象和event事件对象,通过FrameNode可以获取到当前组件的相关信息,如ID、父子节点、组件大小等等,详情请见官网文档FrameNode。用户可根据自己实际需要获取对应信息上报,当前案例仅获取ID与相对屏幕的位置为例子。

下面代码中registerClickAndGestureListener可以同时注册手势监听与点击事件监听,非必填参数callBack用于处理用户一些自定义埋点逻辑,会在回调函数中带出当前的事件对象event和触发该事件的FrameNode节点,具体请看下面实现:

import { FrameNode, uiObserver, UIObserver } from '@kit.ArkUI';
import { ClickEventListenerCallback, GestureEventListenerCallback } from '@ohos.arkui.UIContext';
import { Callback } from '@kit.BasicServicesKit';

// 这里定义的时候需要注意,因为willClick可以触发手势监听也能触发click点击事情监听,但是这两者的事件对象是不一样的,需要具体区分来开 type ClickAndGestureListenerCallBack = (event: ClickEvent | GestureEvent, node?: FrameNode) => void;

export default class Listener { context: UIContext UIObserver: UIObserver node: FrameNode | undefined event: GestureEvent | ClickEvent | undefined //// 点击和手势事件监听,这里的回调函数是非必填参数,如果开发者有自己的上报逻辑,可以在回调函数中自定义,具体实现请看UI侧注册逻辑 registerClickAndGestureListener(callBack: ClickAndGestureListenerCallBack) { this.UIObserver.on(‘willClick’, (event, node) => { this.node = node this.event = event if (callBack) { callBack(event, node) } }) }

// … } <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

在页面中使用,需要注意的在下面代码中按钮上的id属性为可选配置项,主要目的是用于标识具体点击的是哪个组件,自己定义的id更明显,如不想每一个组件都配置,可以使用FrameNode.getUniqueId接口获取系统分配的ID也可以。

import { promptAction } from '@kit.ArkUI'

@Entry @Component struct Index { @State listener: Listener = new Listener(this.getUIContext())

aboutToAppear(): void { // 点击监听 this.listener.registerClickListener((event, node) => { // 这里在注册的时候传递回调函数给到callBack处理自己的逻辑,回调中可以拿到对应事件的事件对象和触发事件的FrameNode节点 // 这里就使用点击那个按钮,获取对应按钮的组件id并使用toast弹窗展示做一个简单示例 promptAction.showToast({ message: node?.getId() }) }) } @Builder ListenerTextBuilder() { // 埋点数据反显 Text(</span></span><span class="javascript"><span class="hljs-function"><span class="hljs-params"><span class="javascript"> ClickNodeId: ${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.listener.node?.getId()} ScreenPositionX: ${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.listener.node?.getPositionToScreen().x} ScreenPositionY: ${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.listener.node?.getPositionToScreen().y} ScrollNodeId: ${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.listener.scrollInfo?.id} ScrollOffset: ${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.listener.scrollInfo?.offset} ScrollStartTime: ${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.listener.scrollStartTime} ScrollEndTime: ${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.listener.scrollEndTime} CurrentRouterInfoName: ${</span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.listener.routerInfo?.to} </span></span></span></span><span class="hljs-function"><span class="hljs-params">).hitTestBehavior(HitTestMode.Transparent) }

build() { Column({ space: 8 }) { // 下面按钮中的id属性如不想每一个都配置可以在埋点反显逻辑中使用getUniqueId获取系统分配的ID Button(‘点击事件按钮1’).onClick(() => { // 处理对应点击逻辑 }).id(‘clickButton1’) Button(‘点击事件按钮2’).onClick(() => { // 处理对应点击逻辑 }).id(‘clickButton2’) Button(‘按下手势按钮1’).gesture(TapGesture({ count: 1 }).onAction(() => { // 处理对应点击逻辑 })).id(‘TapGestureButton1’) Button(‘按下手势按钮2’).gesture(TapGesture({ count: 1 }).onAction(() => { // 处理对应点击逻辑 })).id(‘TapGestureButton2’) }.width(‘100%’).height(‘100%’) .justifyContent(FlexAlign.Center) .overlay(this.ListenerText) } } <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

页面滚动:记录用户页面滚动的位置和时间。

页面滚动监听调用UIObserver.on('scrollEvent')实现,记录滚动开始时间和结束时间,同时在滚动结束的时候显示对应的滚动距离offset,实现代码如下。

ps:当前实现的监听是页面中任何一个滚动组件List、Scroll等只要触发了滚动都该监听都能识别到,并且可以在回调的info中拿到滚动容器组件的ID,如需要精准监听对应的滚动组件可以参考官网示例

// 滚动监听
registerScrollListener(callBack?: Callback<uiObserver.ScrollEventInfo>) {
  this.UIObserver.on('scrollEvent', (info) => {
    this.scrollInfo = info
    if (info.scrollEvent == uiObserver.ScrollEventType.SCROLL_START) {
      this.startTime = Date.now()
    }
    if (info.scrollEvent == uiObserver.ScrollEventType.SCROLL_STOP) {
      this.endTime = Date.now()
    }
    if (callBack) {
      callBack(info)
    }
  })
}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

页面中注册

aboutToAppear(): void {
  ...
  // 滚动监听,这里可以根据自己需要传递回调函数
  this.listener.registerScrollListener()
}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

路由跳转:记录用户跳转页面,统计页面停留时间

路由跳转监听调用UIObserver.on('navDestinationSwitch')实现,代码如下:

这里仅介绍navigation路由,router路由监听实现使用UIObserver.on('routerPageUpdate')。

// 注册路由跳转监听
registerNavDestinationUpdateListener(callBack?: Callback<uiObserver.NavDestinationSwitchInfo>) {
  this.UIObserver.on('navDestinationSwitch', (info) => {
    this.routerInfo = info
    if (callBack) {
      callBack(info)
    }
    // 这里需要获取最后一次停留的页面名称和进入时间,方便后期相关埋点逻辑
    if (info.to != 'navBar') {
      this.lastRouterName = info.to.name
    } else {
      this.lastRouterName = info.to
    }
    this.lastRouterNameStartTime = Date.now()
  })
}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

页面中注册,页面中注册的时候传递自己的处理逻辑,获取页面跳转时间统计页面停留时长。

aboutToAppear(): void {
  ...
  // 路由监听
  this.listener.registerNavDestinationUpdateListener((info) => {
    let toName: ResourceStr = ''
    // 跳转页面触发回调,自定义上报逻辑统计页面停留时间
    if (info.to != 'navBar') {
      toName = info.to.name
    } else {
      toName = info.to
    }
</span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="hljs-keyword">if</span></span></span></span><span class="hljs-function"><span class="hljs-params"> (toName != </span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="hljs-keyword">this</span></span></span></span><span class="hljs-function"><span class="hljs-params">.listener.lastRouterName) {
  promptAction.showToast({ message: `</span></span><span class="javascript"><span class="hljs-function"><span class="hljs-params"><span class="javascript">页面停留时间${</span></span></span><span class="hljs-built_in"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-built_in">Date</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.now() - </span></span></span><span class="hljs-keyword"><span class="hljs-function"><span class="hljs-params"><span class="javascript"><span class="hljs-keyword">this</span></span></span></span></span><span class="hljs-function"><span class="hljs-params"><span class="javascript">.listener.lastRouterNameStartTime}ms</span></span></span></span><span class="hljs-function"><span class="hljs-params">` })
}

}) } <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

实现效果

FAQ

Q:Listener监听是根据context范围来的吗?用应用级的context注册监听是应用级监听?

A:Listener所有监听都是根据UIContext范围全局生效的,因为使用必须与UIContext强绑定,所以在首页的aboutappear里面实现的注册。

Q:手势事件和click事件如何区分监听?

A:当前提供的UIObserver.on('willClick')方法可以同时监听手势事件和click事件,只是回调方法中带出来的事件对象参数不同(手势事件带出来参数为GestureEvent,点击事件带出参数为ClickEvent),其它没什么区别。

</markdown>

升级HarmonyOS后,发现手机的游戏性能也有了显著提升。

找HarmonyOS工作还需要会Flutter技术的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

太强了。有交流的方式吗 wx:asd3306642

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

大佬,能共享下源码吗
回到顶部