Android 埋点之点击事件上报

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

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

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

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

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

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

View源码如下:

ini 复制代码
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提取业务属性,然后进行业务处理;
kotlin 复制代码
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。

kotlin 复制代码
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即可;

kotlin 复制代码
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集合,这样可以防止同一个页面重复查找。

有需要的同学,可以直接拿到走哈。

相关推荐
sun0077005 小时前
android ndk编译valgrind
android
AI视觉网奇6 小时前
android studio 断点无效
android·ide·android studio
jiaxi的天空6 小时前
android studio gradle 访问不了
android·ide·android studio
No Silver Bullet7 小时前
android组包时会把从maven私服获取的包下载到本地吗
android
catchadmin7 小时前
PHP serialize 序列化完全指南
android·开发语言·php
tangweiguo030519878 小时前
Kable使用指南:Android BLE开发的现代化解决方案
android·kotlin
00后程序员张11 小时前
iOS App 混淆与资源保护:iOS配置文件加密、ipa文件安全、代码与多媒体资源防护全流程指南
android·安全·ios·小程序·uni-app·cocoa·iphone
柳岸风12 小时前
Android Studio Meerkat | 2024.3.1 Gradle Tasks不展示
android·ide·android studio
编程乐学12 小时前
安卓原创--基于 Android 开发的菜单管理系统
android
whatever who cares14 小时前
android中ViewModel 和 onSaveInstanceState 的最佳使用方法
android