Android AOP方案(一)——AspectJ

5,772 阅读3分钟

介绍

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来实现我们的功能,以监听FragmentonResume生命周期为例。虽然FragmentManagerregisterFragmentLifecycleCallbacks方法能够监听Fragment的生命周期,但是该API是在Android 8.0的版本才新增的,旧版本的Android无法使用。且针对现在的support、androidx等依赖库,无法做到统一的处理,比较麻烦。

第1步:新建一个Android project

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

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项目比较大时编译时间明显加长。