全埋点(自动埋点)的核心诉求是:不修改业务代码,全局采集用户点击行为。View.OnClickListener 代理方案是最直观的思路——在 View 设置点击监听时,用一个 Wrapper 拦截所有 onClick 调用,先执行埋点逻辑,再转发给原始监听器。
一、核心原理
Android 中所有点击事件最终通过 View.setOnClickListener() 注册。如果能在这个入口做一层代理,就能捕获所有点击。
实现方式:在 Application 或 BaseActivity 中,通过反射或 Hook 替换 WMS 层的点击分发,但更简单的方式是利用 LayoutInflater.Factory2 在布局解析阶段拦截所有 View 创建。
二、代理实现
class TrackingOnClickListener( |
三、全局拦截方案
配合 LayoutInflater Hook,在 View 创建时自动替换 OnClickListener:
class WrapperFactory(private val delegate: LayoutInflater.Factory2) : LayoutInflater.Factory2 { |
在 Application 中注册:
// 在 Application.onCreate 中通过 registerActivityLifecycleCallbacks |
四、局限性
- 动态设置的 Listener:通过
setOnClickListener在运行时动态设置的监听器无法被 LayoutInflater Factory 拦截,需额外 HookView.setOnClickListener()。 - Dialog/Toast/PopupWindow:这些窗口不经过 Activity 的 LayoutInflater,需单独处理。
- Lambda 表达式:Kotlin 中
view.setOnClickListener { }会在编译期生成匿名内部类,反射替换时需注意类型兼容性。 - 性能影响:每次点击多一层代理调用,开销可忽略不计。
面试常考问题
Q1:为什么不用 AspectJ 直接 Hook onClick 方法?
OnClickListener 代理方案的优势是纯运行时方案,无需 Gradle 插件、AOP 编译器或字节码修改。AspectJ 是编译期方案,对项目构建流程有侵入性。两者选型取决于团队技术栈:代理方案轻量但覆盖度有限;AspectJ 覆盖全面但维护成本高。
Q2:如何处理 RecyclerView item 的点击?
RecyclerView 的 item 点击通常在 Adapter 的 onBindViewHolder 中设置,代理方案需确保 Adapter 设置监听器时走代理流程。可以在 RecyclerView 的 setAdapter 上做一层 Hook,或直接在 ViewHolder 的 itemView.setOnClickListener 拦截。
Q3:View 的内部 clickable 属性与代理的关系?
代理不改变 View 的 clickable 状态。如果 View 没有设置 OnClickListener 且 clickable=true,点击事件仍会触发但不会被代理拦截,因为它们没有注册监听器。全埋点方案通常配合触摸事件代理(dispatchTouchEvent)覆盖此类场景。AOSP 源码入口:View.java 中的 setOnClickListener() 方法(frameworks/base/core/java/android/view/View.java)。




