Android AOP 面向切面编程 - AspectJ

AOP 概念

AOP 是 Aspect Oriented Programming 的缩写,意为 面向切面编程,通过预编译和运行期动态代理实现程序功能的统一维护的一种技术。利用 AOP 可以实现对代码的业务逻辑进行隔离,降低各功能间的耦合度。

使用场景: 需求是在类的每个方法中代码执行之前添加一句日志打印,在没有使用 AOP 的情况下,就需要在每个方法中手动添加日志打印,使用了 AOP ,就可以将打印日志代码在编译期间插入方法中,我们维护时二者的逻辑是分开的。

Android AOP 的实现方式

AOP 和 OOP 一样只是一种编程思想,它的实现方式主要有以下几种:

  • APT :AnnotationProcessor 在编译时生成 Java 文件。
  • AspectJ : 在将 .java 文件编译为 .class 文件时进行代码的注入。
  • Javassist : 对编译好的 class 字节码文件进行操作。

![未命名文件 (1)](/Users/xing/Downloads/未命名文件 (1).png)

AOP 术语

JoinPoint:连接点,程序执行过程中明确的点(被拦截的方法,字段,构造器)

Pointcut : 切点,用来描述 JoinPoint 连接点的表达式。比如描述调用 Animal.fly() 方法的地方,则写成

call(* Animal.fly(**))

Advice : 增强,表示在 Pointcut 里面定义的程序点具体要做的操作,通过 before,after,around 来区分是在 Jointpoint 之前,之后还是代替执行的代码。

Aspect :切面,类似 Java 中的类声明,Pointcut 和 Advice 合在一起称作 Aspect。

JoinPoint 说明 Pointcut 语法
method call 函数被调用 call(MethodSignature)
method execution 函数执行内部 execution(MethodSignature)
constructor call 构造函数被调用 call(MethodSignature)
constructor execution 构造函数执行内部 execution(ConstructorSignature)
field get 读成员变量 get(FieldSignature)
field set 写成员变量 set(FieldSignature)
static initialization static 块初始化 staticinitialization(TypeSignature)
handler 异常处理 handler(TypeSignature)只能与 @Before()配合使用
advice execution advice 执行 adviceexecution
Advice 说明
@Before(Pointcut) 执行 JoinPoint 之前
@After(Pointcut) 执行 JoinPoint 之后
@AfterReturning @AfterReturning(pointcut=-“xx”,returning=“returnValue”)
@AfterThrowing @AfterThrowing(pointcut=“xx”,throwing=“throwable”)
@Around(Pointcut) 替代原来的代码,如果要执行原来的代码,需要使用 ProceedingJoinPoint.proceed()

AOP 表达式 & 通配符

execution 基本语法:

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

除了返回类型模式,方法名模式,参数模式外,其他项都是可选的。

  • execution(public * *(…)) : 匹配目标类所有 public 方法, 第一个 * 代表返回类型,第二个 * 代表方法名,而 … 代表任意方法参数。
  • execution(* *To(…)) : 匹配目标类所有以 To 结尾的方法,第一个 * 代表任意返回类型,而 *To 代表任意以 To 结尾的方法。
  • execution(* com.xing.MainActivity.*(…)) : 匹配 MainActivity 中所有的方法,第一个 * 代表任意返回类型,第二个 * 代表方法名任意。
  • execution(*com.xing.demo.MainPresenter+.*(…)) : 匹配 MainPresenter 接口及其所有实现类的方法。
  • execution(* com.xing.*(…)) : 匹配 com.xing 包下所有类的所有方法。
  • execution(* com.xing…*(…)) : 匹配 com.xing 包,子孙包下所有类的所有方法。
  • execution(* com…*.*Dao.find*(…)) : 匹配 com 包,子孙包下以 Dao 结尾的类中所有以 find 为前缀的方法。

Android AspectJ 实现 AOP

Android 中使用 AspectJ 步骤:

(1) 项目根目录 build.gradle 配置 classpath

buildscript {
    repositories {
        google()
        jcenter()
        mavenCentral()   // AspectJ 需要 maven
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
        classpath 'org.aspectj:aspectjtools:1.8.9'
//        classpath 'org.aspectj:aspectjweaver:1.8.9'
    }
}

(2) app build.gradle 添加依赖和 task

apply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.xing.aspectjsample"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

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;
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'org.aspectj:aspectjrt:1.8.9'
}

AspectJ 实战

实战1 - Activity 生命周期函数中添加 log 打印。

/**
 * 定义切面
 */
@Aspect
public class TraceAspect {
    /**
    * 定义切点,拦截 MainActivity 中所有以 on 为前缀的方法
    */
    @Pointcut("execution(* com.xing.aspectjsample.MainActivity.on*(..))")
    public void onLifecycleLog(){}

    /**
    * 在所有以 on 为前缀的方法执行前添加 log.e 打印
    */
    @Before("onLifecycleLog()")
    public void handleLifecycleLog(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Log.e("MainActivity", name + "-------->>>>>" + joinPoint);
    }
}
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "TraceAspect";

    private int count = 12;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        int count = getCount();
    }

    private int getCount() {
        return count;
    }

    @SingleClick
    public void click(View view) {
        Log.e(TAG, "click: ");
        Toast.makeText(this, "the button is clicked", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onStart() {
        super.onStart();
    }


    @Override
    protected void onResume() {
        super.onResume();
    }


    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onRestart() {
        super.onRestart();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

在这里插入图片描述

实战2 - 防止按钮重复点击

防止按钮重复点击思路是:两次点击的时间间隔小于指定值,不产生点击作用。

定义注解 SingleClick

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SingleClick {
    long value() default 1000;
}

定义切面类,拦截类中标注了 SingleClick 注解,参数为 View 的方法进行增强处理。

@Aspect
public class SingleClickAspect {
    private static final String TAG = "SingleClickAspect";

    private long lastClickTime;

    /**
     * 定义切点,标记切点为所有被 @SingleClick 注解修饰的方法
     */
    @Pointcut("execution(@com.xing.aspectjsample.SingleClick * *(..))")
    void singleClickAnnotated() {

    }

    /**
     * 定义一个 Advice,包裹切点方法
     *
     * @param joinPoint
     */
    @Around("singleClickAnnotated()")
    public void handleSingleClick(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法参数
        View view = null;
        for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof View) {
                view = (View) arg;
                break;
            }
        }
        if (view == null) {
            return;
        }
        // SingleClick 注解只修饰在方法上
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 只处理有 SingleClick 注解修饰的方法
        if (!method.isAnnotationPresent(SingleClick.class)) {
            return;
        }
        // 获取到 SingleClick 注解对象
        SingleClick singleClick = method.getAnnotation(SingleClick.class);
        // 获取注解值
        long value = singleClick.value();
        long currentTime = System.currentTimeMillis();
        if (currentTime - lastClickTime > value) {
            lastClickTime = currentTime;
            joinPoint.proceed();
        }
    }
}

Activity 中进行引用:

@SingleClick
public void click(View view) {
	Log.e(TAG, "click: ");
	Toast.makeText(this, "the button is clicked", Toast.LENGTH_SHORT).show();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章