目录
  1. 1. 一、AspectJ 核心概念
  2. 2. 二、Gradle 集成
  3. 3. 三、点击埋点的 Aspect 实现
  4. 4. 四、更丰富的 Pointcut 场景
  5. 5. 五、AspectJ vs 其他方案
  6. 6. 面试常考问题
【全埋点方案系列】AppClick全埋点之AspectJ处理

AspectJ 是面向切面编程(AOP)在 Java 领域的标准实现。在全埋点场景中,AspectJ 可以在编译期将埋点代码织入指定的方法(如 View.OnClickListener.onClick()),实现零业务代码侵入的全自动埋点。

一、AspectJ 核心概念

  • Join Point:代码中的可插入点,如方法调用、字段赋值等。
  • Pointcut:匹配 Join Point 的表达式,定义”在哪里”插入代码。
  • Advice:在匹配的 Pointcut 前后执行的代码,即”插入什么逻辑”。
    • @Before:方法执行前
    • @After:方法执行后
    • @Around:包裹方法执行,可控制是否执行原方法

二、Gradle 集成

// 项目根 build.gradle
buildscript {
dependencies {
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'
}
}

// app/build.gradle
apply plugin: 'android-aspectjx'

aspectjx {
// 排除不需要织入的包
exclude 'com.google', 'androidx', 'com.squareup'
}

三、点击埋点的 Aspect 实现

@Aspect
class ClickTrackingAspect {

// Pointcut:匹配所有 View.OnClickListener 的 onClick 方法执行
@Pointcut("execution(* android.view.View.OnClickListener.onClick(android.view.View))")
fun onClickMethod() {
// 空方法,仅作为 Pointcut 声明
}

@Around("onClickMethod()")
fun aroundClick(joinPoint: ProceedingJoinPoint) {
// 提取被点击的 View
val view = joinPoint.args[0] as? View ?: return

// 1. 埋点采集
trackClick(view)

// 2. 继续执行原始 onClick 逻辑
joinPoint.proceed()
}

private fun trackClick(view: View) {
val context = view.context
val info = buildString {
append("page=${context.javaClass.simpleName}")
append("&view_id=${view.id}")
append("&view_class=${view.javaClass.name}")
// 提取资源名称
try {
val resName = view.resources.getResourceEntryName(view.id)
append("&res_name=$resName")
} catch (_: Exception) {}
}
AnalyticsSDK.track("app_click", info)
}
}

四、更丰富的 Pointcut 场景

// 匹配所有 Activity 的 onCreate(页面进入埋点)
@Pointcut("execution(* android.app.Activity.onCreate(android.os.Bundle))")
fun activityOnCreate() {}

// 匹配所有 AdapterView 的 onItemClick(列表项点击)
@Pointcut("execution(* android.widget.AdapterView.OnItemClickListener.onItemClick(..))")
fun onItemClick() {}

// 匹配自定义注解标记的方法
@Pointcut("execution(@com.example.TrackEvent * *(..))")
fun annotatedMethod() {}

五、AspectJ vs 其他方案

方案 织入时机 覆盖度 性能 集成复杂度
AspectJ 编译期 高(所有 onClick) 无运行时开销 中(Gradle 插件)
ASM 编译期(Transform) 极高 无运行时开销 高(需字节码知识)
Javassist 编译期/运行时 有反射开销 低(源码级 API)
OnClickListener 代理 运行时 极低

面试常考问题

Q1:AspectJ 的织入时机是在 javac 编译之前还是之后?

AspectJ 在 Java 编译为 .class 文件之后、.class 打包为 .dex 之前执行。它修改的是字节码。在 Android Gradle 构建流程中,AspectJX 插件在 Transform 阶段插入,扫描所有 class 文件,对匹配 Pointcut 的方法进行字节码增强。

Q2:@Around 与 @Before/@After 的选择?

全埋点场景推荐 @Around,因为可以在 joinPoint.proceed() 前后分别采集时间,计算点击处理的耗时,同时能通过 try-catch 捕获原始 onClick 中的异常并上报。@Before 无法获取方法返回值或捕获异常。

Q3:AspectJ 如何处理 Kotlin 的 lambda onClick?

Kotlin 中 view.setOnClickListener { } 编译后生成实现 View.OnClickListener 的匿名内部类,其 onClick 方法符合 Pointcut execution(* View.OnClickListener.onClick(View)),可以正常织入。反编译后可见 class 文件中实际生成了一个额外类,AspectJ 对其字节码进行修改。

打赏
  • 微信
  • 支付宝

评论