目录
  1. 1. 一、核心原理
  2. 2. 二、全局设置
  3. 3. 三、优劣势分析
  4. 4. 四、适用场景
  5. 5. 面试常考问题
【全埋点方案系列】AppClick全埋点之View.AccessibilityDelegate代理

AccessibilityDelegate 是 Android 无障碍服务的一部分,每个 View 都可以设置一个 AccessibilityDelegate 来拦截无障碍事件(包括点击)。当 TalkBack 等无障碍服务激活时,用户的触摸交互会通过 sendAccessibilityEvent 回调。利用这一机制可以实现”零代码侵入”的全埋点。

一、核心原理

当辅助功能服务启用时,系统会将触摸事件转换为 AccessibilityEvent 发送给 View。通过在 View 树根节点设置自定义 AccessibilityDelegate,可以在 sendAccessibilityEvent 回调中捕获点击事件(TYPE_VIEW_CLICKED)。

class TrackingAccessibilityDelegate(
private val origin: View.AccessibilityDelegate?
) : View.AccessibilityDelegate() {

override fun sendAccessibilityEvent(host: View?, eventType: Int) {
super.sendAccessibilityEvent(host, eventType)

if (eventType == AccessibilityEvent.TYPE_VIEW_CLICKED && host != null) {
trackClick(host)
}
}

override fun sendAccessibilityEventUnchecked(host: View?, event: AccessibilityEvent?) {
super.sendAccessibilityEventUnchecked(host, event)
event?.takeIf { it.eventType == AccessibilityEvent.TYPE_VIEW_CLICKED }?.let {
host?.let { trackClick(it) }
}
}

private fun trackClick(view: View) {
val info = mapOf(
"class" to view.javaClass.name,
"content_description" to (view.contentDescription?.toString() ?: ""),
"id" to view.id,
"text" to ((view as? TextView)?.text?.toString() ?: "")
)
AnalyticsSDK.track("app_click", info)
}
}

二、全局设置

class TrackingApplication : Application() {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(object : SimpleActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
// 从 DecorView 开始递归设置代理
setGlobalAccessibilityDelegate(activity.window.decorView)
}
})
}

private fun setGlobalAccessibilityDelegate(view: View) {
// 替换已有的 AccessibilityDelegate
val delegate = View::class.java
.getDeclaredMethod("getAccessibilityDelegate")
.invoke(view) as? View.AccessibilityDelegate
view.accessibilityDelegate = TrackingAccessibilityDelegate(delegate)
}
}

三、优劣势分析

优势

  • 真正的零业务代码侵入,无需修改 setOnClickListener
  • 无需反射私有字段 mOnClickListener,比 OnClickListener 代理更合规
  • 能捕获通过无障碍触发的点击(如 TalkBack 双击),覆盖盲操场景

局限性

  • 严重依赖辅助功能状态:若用户未开启 TalkBack 等无障碍服务,sendAccessibilityEvent 不会触发点击事件,埋点数据将大量丢失
  • 兼容性风险:部分 ROM 厂商对 AccessibilityDelegate 有定制行为
  • 性能额外开销:每次点击事件需要额外生成 AccessibilityEvent 对象

四、适用场景

由于严格依赖无障碍服务,该方案不作为独立的全埋点方案使用,通常作为以下场景的补充:

  1. 无障碍友好型 App 的埋点补充
  2. 与其他方案(如 Window.Callback)组合,覆盖特殊场景

面试常考问题

Q1:AccessibilityDelegate 全埋点为什么不能作为主方案?

因为 sendAccessibilityEvent 中的 TYPE_VIEW_CLICKED 事件只有在系统无障碍服务(如 TalkBack、Switch Access)激活时才会有系统将其触摸转换为 AccessibilityEvent。在普通用户场景(未开启辅助功能),点击操作不会经过 AccessibilityDelegate,因此无法捕获点击。AOSP 源码路径:View.java 中的 onInitializeAccessibilityEvent()sendAccessibilityEvent() 方法。

Q2:能否通过代码强制开启无障碍服务来弥补?

不能。无障碍服务需要在系统设置中由用户手动开启,Android 出于安全和隐私考虑不允许应用通过代码自启动无障碍服务。部分厂商提供了快捷跳转到无障碍设置页面的 Intent,但最终开关权在用户手中。

Q3:如何区分真实点击和 TalkBack 模拟点击?

sendAccessibilityEvent 中可以通过 host.isScreenReaderFocusable() 或检查 AccessibilityEvent 的来源(getSource())来辅助判断,但无法可靠区分触摸点击和无障碍模拟点击。如果需要精确区分,建议配合 Window.Callback 代理方案使用。

打赏
  • 微信
  • 支付宝

评论