美文网首页
Android 埋点之点击事件上报

Android 埋点之点击事件上报

作者: BlueSocks | 来源:发表于2023-11-15 18:11 被阅读0次

点击事件的埋点,需要处理两个问题:

  • 如何将业务模块跟埋点模块隔离,减少对业务模块的侵入;
  • 基于第一点,怎么才能获取获取业务模块的属性呢,利用activity 的pageId。

1. 业务模块跟埋点模块如何隔离

基本思路:利用ActivityLifecycleCallbacks对Activity的生命周期进行监听,在其创建时获取设置了点击事件的View,当触发了点击事件,则上报相关信息。 只要遍历decorView就可以找出符合条件的view,那么如何监听点击事件呢?

方式1:代理原有的点击事件监听;

方式2:设置setAccessibilityDelegate监听,这是在点击事件后会触发的事件,我采取的是这种方式;

View源码如下:

public boolean performClick() {

    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);//点击事件回调
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);//这是我们的切入点
    return result;
}

2. 业务模块如何暴露相关属性

方式1:业务模块实现自定义接口,对外暴露相关属性;

方式2:业务模块利用view的setTag函数配置相关属性,我采取的是这种方式;

3.代码实现

ClickEventHelper 是点击事件处理的工具类,负责对符合条件的view绑定监听事件以及对上报前进行业务处理;

  • 注释1:业务属性实体类,,可根据实际情况进行定义,pageId为view所属的页面id,viewId为view表示,有别于xml 的id;
  • 注释2:负责查找设置了绑定事件的view,以及设置accessibilityDelegate监听事件;
  • 注释3:适配fragment的场景;
  • 注释4:查找设置了绑定事件的view,内部用到了递归,注意ViewGroup也可能设置了点击事件;
  • 注释5:给view设置accessibilityDelegate监听;
  • 注释6:处理监听回调,通过tag提取业务属性,然后进行业务处理;
class ClickEventHelper {
    data class ViewParams(val pageId: Int, val viewId: Int)//1
    companion object {
        const val TAG = "ClickEventHelper"
    }
    fun bindAccessibilityDelegateToViews(activity: Activity) {//2
        val hasOnClickListenersViewList = mutableListOf<View>()
        val rootView = activity.window.decorView as ViewGroup
        rootView.forEach {
            findHasClickListenerView(it, hasOnClickListenersViewList)
        }
        hasOnClickListenersViewList.forEach {
            bindToView(it)
        }
    }

    fun bindAccessibilityDelegateToViews(fragment: Fragment) {//3
        val hasOnClickListenersViewList = mutableListOf<View>()
        val rootView = fragment.requireView() as ViewGroup
        rootView.forEach {
            findHasClickListenerView(it, hasOnClickListenersViewList)
        }
        hasOnClickListenersViewList.forEach {
            bindToView(it)
        }
    }

    private fun findHasClickListenerView(parentView: View, list: MutableList<View>) {//4
        when (parentView) {
            is ViewGroup -> {
                if (parentView.hasOnClickListeners()) {//layout 也有可能有点击事件
                    list.add(parentView)
                }
                parentView.forEach {
                    findHasClickListenerView(it, list)
                }
            }
            else -> {
                if (parentView.hasOnClickListeners()) {
                    list.add(parentView)
                }
            }
        }
    }

    private fun bindToView(view: View) {//5
        view.accessibilityDelegate = object : View.AccessibilityDelegate() {
            override fun sendAccessibilityEvent(view: View, eventType: Int) {//6
                val p = view.tag as? ViewParams
                p?.apply {
                    val pageId = p.pageId
                    val viewId = p.viewId
                    //TODO 做相应的业务处理
                    //TODO 埋点上报
                }
            }
        }
    }
}

对ActivityLifecycleCallbacks的实现就比较简单了,监听Activity的创建,调用ClickEventHelper对应的函数;因为无法监听Fragment的创建,所以需要在其生命周期函数中调用bindAccessibilityDelegateToViews函数。 大家是否注意到注释2,用到了协程,为什么呢?其实跟onActivityCreated的回调时机有关。onActivityCreated函数比Activity的onCreate先执行,使用协程是保证bindAccessibilityDelegateToViews函数在onCreate执行完再触发,因为一般都是在onCreate函数中对view setTag。

class AppActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {

    private val clickEventHelper = ClickEventHelper()

    override fun onActivityCreated(p0: Activity, p1: Bundle?) {//1
        val lifecycleScope = ((p0 as? LifecycleOwner)?.lifecycleScope) ?: return
        clickEventHelper.bindAccessibilityDelegateToViews(p0)
        lifecycleScope.launchWhenCreated {//2
            clickEventHelper.bindAccessibilityDelegateToViews(p0)
        }
    }


    override fun onActivityStarted(p0: Activity) {}
    override fun onActivityResumed(p0: Activity) {}
    override fun onActivityPaused(p0: Activity) {}
    override fun onActivityStopped(p0: Activity) {}
    override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {}
    override fun onActivityDestroyed(p0: Activity) {}
}

使用方式:在Activity或Fragment对view设置tag即可;

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.nameTv.setOnClickListener {
            Log.d("MainActivity","点击了")
        }
        binding.nameTv.setTag(ClickEventHelper.ViewParams(10, 1010))
    }
}

4.优化空间

  • 上述代码只对设置了OnClickListener监听的view进行了处理,如果需要处理其他点击事件,例如CheckBox、RadioButton,对findHasClickListenerView函数进行扩展即可;
  • 如果页面中view太多,可考虑异步处理查找的逻辑;
  • 可以使用HashMap缓存符合条件的view,key为activity、fragment全类名,value为view集合,这样可以防止同一个页面重复查找。

相关文章

  • 前端埋点上报

    本文所说的埋点上报,只包含两种:点击上报(click)、曝光上报(show)。 整体思路: 点击上报: 使用 wi...

  • 点击事件无痕埋点解决方案--ClickTrace

    背景 最近App开发中遇到了无痕埋点的需求。 所谓无痕埋点,即在App上线后可以上报通用数据(如生命周期事件、点击...

  • 移动APP用户数据采集及分析案例

    一个app要获取用户的日常点击数据,各个事件的点击及使用时长等数据,需要对app进行埋点,通过埋点数据上报,然后汇...

  • Android ASM插件之埋点上报注解

    ASM埋点上报插件 插件功能[#%E6%8F%92%E4%BB%B6%E5%8A%9F%E8%83%BD] 插件源...

  • 数据埋点之六:埋点需求设计

    主要埋点事件: 点击事件点击事件,用户点击按钮即算点击事件,不管点击后有无结果;点击一次记一次。 曝光事件成功打开...

  • Android埋点技术总结

    1.埋点技术的分类 1.1 代码埋点:代码埋点是指在某个事件发生时调用数据发送接口上报数据。例如开发人员按照产品/...

  • 监听 app被杀死

    APP有时候被杀死,需要埋点上报事件,这时候 可以这么做。。 一: 添加通知的监听 二: 事件处理

  • 2020-02-26 如何设计数据埋点

    案例:对活动入口的点击量进行埋点统计? 三种简单的前端埋点事件 曝光事件:记录页面被浏览次数,记录页面流量所用。 ...

  • Android-全自动埋点方法论-下

    前面提到了全自动埋点的范围和方法,这个章节主要记录一下关于点击事件采集的相关方法论。 Android开发基础知识 ...

  • Android全埋点方案调研

    1 行业内全埋点技术方案调研 调研文章链接: 网易云音乐Android 自动埋点实践 网易HubbleData之A...

网友评论

      本文标题:Android 埋点之点击事件上报

      本文链接:https://www.haomeiwen.com/subject/oooswdtx.html