什么是AOP

以下是维基百科上对AOP的介绍:

面向切面的程序设计(Aspect-oriented programming,AOP,又译作面向方面的程序设计、剖面导向程序设计)是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提高程序代码的模块化程度。通过在现有代码基础上增加额外的通知(Advice)机制,能够对被声明为“切点(Pointcut)”的代码块进行统一管理与装饰,如“对所有方法名以‘set*’开头的方法添加后台日志”。该思想使得开发人员能够将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不降低业务代码的可读性。面向切面的程序设计思想也是面向切面软件开发的基础。

面向切面的程序设计将代码逻辑切分为不同的模块(即关注点(Concern),一段特定的逻辑功能)。几乎所有的编程思想都涉及代码功能的分类,将各个关注点封装成独立的抽象模块(如函数、过程、模块、类以及方法等),后者又可供进一步实现、封装和重写。部分关注点“横切”程序代码中的数个模块,即在多个模块中都有出现,它们即被称作横切关注点(Cross-cutting concerns, Horizontal concerns)。

什么是AspectJ

以下是维基百科上对AspectJ的介绍:

AspectJ是在PARC为Java编程语言创建的面向方面的编程(AOP) 扩展。它在Eclipse Foundation开源项目中可用,既可独立使用,也可集成到Eclipse 中。通过强调最终用户的简单性和可用性,AspectJ 已成为广泛使用的 AOP 事实标准。自 2001 年首次公开发布以来,它使用类似 Java 的语法,并包含用于显示横切结构的IDE 集成。

AspectJ在Android中的使用

本篇主要记录的是AspectJ在Android中的使用,故不会记录AspectJ的安装以及使用AspectJ编译aj文件相关的知识点,如果需要了解这部分的知识,可以参考邓凡平老师写的深入理解Android之AOP。

下面举个例子来说明AspectJ在Android中的使用:

比如我们希望在某个Activity的onCreate生命周期方法开始打印一行日志,当然你可以直接编码来实现,但是如果使用AspectJ面向切面编程的方式,可以这么做:

  1. 在Android项目根目录的build.gradle文件中加入如下代码
dependencies {
    classpath "com.android.tools.build:gradle:4.2.2"
    classpath 'org.aspectj:aspectjtools:1.9.7'
    classpath 'org.aspectj:aspectjweaver:1.9.7'
}
  1. 在app/build.gradle文件中加入如下代码
android {
	...
}

dependencies {
    implementation 'org.aspectj:aspectjrt:1.9.7'
    ...
}

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

final def log = project.logger
final def variants = project.android.applicationVariants

variants.all { variant ->
    if (!variant.buildType.isDebuggable()) {
        log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
        return;
    }
    JavaCompile javaCompile = variant.javaCompile
    javaCompile.doLast {
        String[] args = ["-showWeaveInfo",
                         "-1.8",
                         "-inpath", javaCompile.destinationDir.toString(),
                         "-aspectpath", javaCompile.classpath.asPath,
                         "-d", javaCompile.destinationDir.toString(),
                         "-classpath", javaCompile.classpath.asPath,
                         "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
        log.debug "ajc args: " + Arrays.toString(args)
        MessageHandler handler = new MessageHandler(true);
        new Main().run(args, handler);
        for (IMessage message : handler.getMessages(null, true)) {
            switch (message.getKind()) {
                case IMessage.ABORT:
                case IMessage.ERROR:
                case IMessage.FAIL:
                    log.error message.message, message.thrown
                    break;
                case IMessage.WARNING:
                    log.warn message.message, message.thrown
                    break;
                case IMessage.INFO:
                    log.info message.message, message.thrown
                    break;
                case IMessage.DEBUG:
                    log.debug message.message, message.thrown
                    break;
            }
        }
    }
}
  1. 创建一个Java类并使用@Aspect注解,如下代码
import android.util.Log;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MethodAspect {

    private static final String TAG = "MethodAspect";

	// *表示方法返回值为任意值,..表示方法参数为任意值
    @Pointcut("execution(* com.example.testaspectj.MainActivity.onCreate(..))")
    public void jointPoint() {}

	// 这里的jointPoint()必须和上面的方法名对应
    @Before("jointPoint()")
    public void beforeOnCreate() {
        Log.e(TAG, "this message is printed before onCreate...");
    }

	// 上面两个方法也可以合并写成下面一个方法
	// @Before("execution(* com.example.testaspectj.MainActivity.onCreate(..))")
    // public void beforeOnCreate(JoinPoint joinPoint) {
    //     Log.e(TAG, "this message is printed before onCreate...");
    // }

}
  1. 运行代码启动MainActivity,MainActivity不用做任何改动,直接用创建Android新项目时的代码。程序运行后控制台会有如下打印:

    在app/build/intermediates/javac/debug/classes/目录下,你可以看到MainActivity.class文件,通过AndroidStudio反编译后源码如下:

    可以看到onCreate方法中插入了一行新代码:
MethodAspect.aspectOf().beforeOnCreate();

AspectJ知识点

AspectJ术语

  • JPoint:代码可注入的点,比如一个方法的调用处或者方法内部、“读、写”变量等。
  • Pointcut:用来描述 JPoint 注入点的一段表达式,比如:调用 Animal 类 fly 方法的地方,call(* Animal.fly(…))。
  • Advice:常见的有 Before、After、Around 等,表示代码执行前、执行后、替换目标代码,也就是在 Pointcut 何处注入代码。
  • Aspect:Pointcut 和 Advice 合在一起称作 Aspect。

AspectJ语法


Pointcut 中的 Signature 参考:

以上的 Signature 都是由一段表达式组成,且每个关键词之间都有“空格”,下面是对关键词的解释:

Pointcut 语法熟悉了之后,Advice 就显得很简单了,它包含以下几个:


AspectJ在Android中的应用还可以使用这个开源库:https://github/HujiangTechnology/gradle_plugin_android_aspectjx这个库简化了直接在Android项目中使用AspectJ的门槛,但是对于gradle插件的版本有要求(目前支持的gradle插件最高版本为3.6.1)

参考

  • 深入理解Android之AOP
  • AOP 之 AspectJ 全面剖析 in Android
  • AspectJ在Android中的使用

源码

本篇Demo的源码可以在这里下载:https://github/yubo725/android-aspectj-demo/tree/v0.1

更多推荐

Android AOP编程(一)——AspectJ基础知识