介绍

AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承特性语言的缺点(如Java),AOP具体实现有很多种方式,AspectJ 只是其中一种。

AspectJ通过注解的形式来标注切入点、切入对象等,然后在代码编译期间将代码织入到java的字节码中。

更多AspectJ详细的资料,可以参考其官网:

www.eclipse.org/aspectj/

AspectJ 注解

@Aspect

该注解用来标注一个类,标明当前类是切面类,以便AspectJ能够识别。如:

@Aspect
public class FragmentAspectJInjector{
}


@Pointcut
标注一个方法,用来定义切点,参数为切点表达式。如定义一个切点是在Fragment的onResume生命周期方法执行的时候:
@Pointcut("execution(* android.app.Fragment+.onResume(..))")
public void fragmentOnResumePointcut(){
}


@Around,@Before,@After

标注一个方法,定义具体织入的代码,参数为切点表达式。 这里可以使用“&&、||、!”来组合不同的Pointcut定义。如我们在Fragment的onResume生命周期前打印下当前的Fragment对象:

@Around("fragmentOnResumePointcut()")
public void fragmentOnResume(final ProceedingJoinPoint joinPoint) throws Throwable{
Object target = joinPoint.getTarget();
Log.e(TAG, "fragmentOnResume: fragment = " + target);
joinPoint.proceed();
}


AspectJ 注解还有很多,如@AfterReturning、@AfterThrowing等,这里不再一一列举,大家有兴趣可以查看官方文档。上述这些注解基本已经可以满足我们的需求了。

AspectJ 使用

上文我们介绍了AspectJ相关的一些基础知识,下面我们来实践如何在Android工程中使用AspectJ。

目前github上面已经有很多开源的项目,比如:

AspectJX: github.com/HujiangTech…

Hugo: github.com/JakeWharton…

gradle-android-aspectj-plugin: github.com/uPhyca/grad…

下面我们将直接使用AspectJX来实现我们的功能,以监听Fragment的onResume生命周期为例。虽然FragmentManager的registerFragmentLifecycleCallbacks方法能够监听Fragment的生命周期,但是该API是在Android 8.0的版本才新增的,旧版本的Android无法使用。且针对现在的support、androidx等依赖库,无法做到统一的处理,比较麻烦。

第1步:新建一个Android project

在新建的APP项目中,修改MainActivity的布局文件并加载一个Fragment,代码如下:

activity_main.xml
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
android:id="@+id/fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />


MainActivity.java
public class MainActivity extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getFragmentManager().beginTransaction().replace(R.id.fragment, new BlankFragment()).commit();
}
}


第2步:添加AspectJX依赖

根据AspectJX的github文档,我们在项目根目录的build.gradle里添加依赖

buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0-alpha07'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'
}
}


在APP module中的build.gradle里应用插件

apply plugin: 'android-aspectjx'


第3步:添加切面类

@Aspect
public class FragmentAspectJInjector{
private static final String TAG = "FragmentAspectJInjector";
@Pointcut("execution(* android.app.Fragment+.onResume(..))")
public void fragmentOnResumePointcut(){
}
@Around("fragmentOnResumePointcut()")
public void fragmentOnResume(final ProceedingJoinPoint joinPoint) throws Throwable{
Object target = joinPoint.getTarget();
Log.e(TAG, "fragmentOnResume: fragment = " + target);
joinPoint.proceed();
}


第4步:构建并运行APP

通过logcat窗口可以看到如下输出:

E/FragmentAspectJInjector: fragmentOnResume: fragment = BlankFragment{8fafb1e #1 id=0x7f0800a4}\


可以看到我们的切点已经织入成功了。

AspectJ 缺点

如果相应的class没有实现相应的切点方法将无法织入,如上文中的没有BlankFragment实现onResume方法的话,将无法织入代码。

无法处理Lambda语法

会有一系列兼容性问题,如R8、gradle版本不同等

性能较差,APP项目比较大时编译时间明显加长。