文章转自:https://mp.weixin.qq.com/s/EaHrD4A8TeADDPUQEoKb-g

概述

AspectJ 是 Java 语言 AOP(面向切面编程)的一种实现方案。

AspectJ 有两种实现方式:

  1. 使用 Java 语言和注解,然后通过 AspectJ 提供的编织器,编织代码到目标 class 文件;
  2. 直接使用 AspectJ 语言编写,然后使用 ajc 编译器用来编译 aj 文件,生成 java 标准的 class 文件。

AspectJ 语言的语法和 Java 一样,只是比 Java 语言多了一些关键字,但由于 Android Studio 并没有提供 AspectJ 语法的支持,所以在 Android 开发中使用 AspectJ 只能使用注解的方式来实现。

下面通过注解的实现方式来解释一些基本的概念。

基本概念

Aspect(切面)

一个切面是一个独立的功能实现,一个程序可以定义多个切面,定义切面需要新建一个类并加上 @Aspect 注解。

例如:SampleAspect 就是一个切面 ,这个切面实现了打印所有 Activity 中 onCreate() 方法耗时的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 @Aspect
public class SampleAspect {
@Pointcut("execution(void android.support.v4.app.FragmentActivity+.onCreate(..))")
public void activityOnCreate() {

}
@Around("activityOnCreate()")
public Object activityOnCreateTime(ProceedingJoinPoint joinPoint) {
Object object = null;
long startTime = System.currentTimeMillis();
try {
object = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.d("chao","activityOnCreateTime:"+(System.currentTimeMillis() - startTime));
return object;
}
}

JointPoint(链接点)

链接点代表了程序中可以切入代码的位置,包括函数的调用和执行,类的初始化,异常处理等,链接点就是利用 AspectJ 可以侵入修改的地方。

例如 Activity 中 onCreate() 方法的调用和执行,Activity 的初始化,构造方法的执行等,可以在这些 JointPoint(链接点)切入自己想要的代码。

PointCut(切点)

切点是具体的链接点,切点定义了需要织入代码的链接点,切点的定义有专门的语法。

例如:下面这个就代表一个切点,这个切点表示了所有 Activity 以及其子类的 onCreate() 方法的执行。

1
2
3
4
@Pointcut("execution(void android.support.v4.app.FragmentActivity+.onCreate(..))")
public void activityOnCreate() {

}

Advice(通知)

通知代表对切点的监听,可以在这里编写需要织入的代码。通知包括:@Before 方法执行前,@After 方法执行后,@Around 方法执行前后。

例如:下面分别表示了切点 activityOnCreate 的执行前、执行后、执行前后的监听,其中 @Around 需要自己处理方法的执行,并且必须放在 @Before@After 之前。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Around("activityOnCreate()")
public Object activityOnCreateTime(ProceedingJoinPoint joinPoint) {
Object object = null;
long startTime = System.currentTimeMillis();
try {
object = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.d("chao","activityOnCreateTime:"+(System.currentTimeMillis() - startTime));
return object;
}

@Before("activityOnCreate()")
public void onCreateBefore(JoinPoint joinPoint) {
Log.d("chao", "onCreateBefore" + joinPoint.getSignature().getDeclaringType() + ":" + joinPoint.getSignature().getDeclaringTypeName());
}
@After("activityOnCreate()")
public void onCreateAfter(JoinPoint joinPoint) {
Log.d("chao", "onCreateAfter" + joinPoint.getSignature().getDeclaringType() + ":" + joinPoint.getSignature().getDeclaringTypeName());
}

匹配表达式

上面讲到了定义切点时需要用到专门的语法:匹配表达式。匹配表达式分为以下几种类型:

方法类型

1
1[!] [@Annotation] [public,protected,private] [static] [final] 返回值类型 [类名.]方法名(参数类型列表) [throws 异常类型]

定义方法切点时需要用到方法类型的匹配表达式,其中 execution() 代表了方法的执行,[] 中的内容为可选。

如果是构造方法只需要把方法名替换为 new。例如:

1
2
3
4
5
6
7
8
9
10
11
@Pointcut("execution(@com.sohu.aspectj.AspectLog public static final boolean
com.sohu.aspectj.MainActivity.getIsAnnotation(java.lang.String,boolean)throws
java.lang.Exception)")
public void activityIsFirst() {

}
构造方法的切入点
@Pointcut("execution(public com.sohu.aspectj.KotlinClassFile.new(java.lang.String,boolean))")
public void kotlinClassInit() {

}

Android集成AspectJ

在 AndroidStudio 中使用 AspectJ 需要分三步:

引入第三方注解包

在需要编写切面的 Module 中引入 'org.aspectj:aspectjrt:1.8.7' 里面包含了注解等代码。

1
2
3
4
dependencies {
//aspectj注解工具包
implementation 'org.aspectj:aspectjrt:1.8.7'
}

引入 Gradle 插件

使用 AspectJ 的功能,必须通过插件来处理插入的代码。Gradle 插件的作用是处理切面类,植入代码。目前针对 Android 来说并没有官方 Gradle 插件,需要自己编写或者使用第三方成熟的插件。

下图所示是本地自定义的一个 Gradle 插件,使用这种本地 Gradle 插件的方式,则只需要在主 Module 的 build.gradle 中引入插件即可,具体实现细节下个章节会讲到。

编写切面类

编写切面类,编写切点和通知代码

Gradle 插件的实现

Gradle 插件实现如果不需要发布插件,则可以再编写本地插件。自定义插件的方式这里不再赘述,感兴趣的小伙伴可以参考官方文档。