Android 埋点之点击事件上报

2023-11-15  本文已影响0人  BlueSocks

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

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绑定监听事件以及对上报前进行业务处理;

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.优化空间

上一篇 下一篇

猜你喜欢

热点阅读