Android AOP 面向切面编程 AspectJ

255 阅读1分钟

适用场景

  • 统计埋点
  • 日志打印/打点
  • 数据校验
  • 行为拦截
  • 性能监控
  • 动态权限控制

AOP 是什么,一图胜前言

相对于我们的流程图都是纵向的,现在我们来介绍一种横向的编程模型

image.png

show me the code

工程 build.gradle 中顶部新增

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.3"
        classpath 'org.aspectj:aspectjtools:1.9.6'
    }
}

新建一个java library 名称为annotation 新建一个Android Library 名称为aspect build.gradle

dependencies {
    implementation project(":annotation")
    api "org.aspectj:aspectjrt:1.9.6
}


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

final def log = project.logger
//注意:如果是 Library Module 下面这个行代码中的 applicationVariants 应该替换为 libraryVariants
final def variants = project.android.libraryVariants
//在构建工程时,执行编织
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
            }
        }
    }

app build.gradle

dependencies {
    implementation 'org.aspectj:aspectjrt:1.9.6'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    implementation 'androidx.navigation:navigation-fragment:2.4.1'
    implementation 'androidx.navigation:navigation-ui:2.4.1'
    implementation project(":annotation")
    api project(":aspect")
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

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

final def log = project.logger
//注意:如果是 Library Module 下面这个行代码中的 applicationVariants 应该替换为 libraryVariants
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
            }
        }
    }

定义一个注解(可选,后面介绍)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnceClick {

    /**
     * 快速点击的间隔
     */
    long value() default 1000;
}

定义切入面

@Aspect
public class ClickOnceAspect {
    private static final String TAG = "sansui";
    private static final String POINTCUT_METHOD =
        "execution(@com.example.annotation.ClickOnce * *(..))";

    /**
     * 最近一次点击的时间
     */
    private long mLastTime;
    /**
     * 最近一次点击的控件ID
     */
    private int mLastId;

    @Pointcut(POINTCUT_METHOD)
    public void clickOnce() {}

    @Around("clickOnce() && @annotation(clickOnce)")
    public void aroundClickOnce(ProceedingJoinPoint joinPoint, ClickOnce clickOnce)
        throws Throwable {
        View v = null;
        for (Object arg : joinPoint.getArgs()) {
            if (arg instanceof View) {
                v = (View) arg;
            }
        }
        if (v != null) {
            long currentTime = System.currentTimeMillis();
            if ( v.getId() == mLastId && currentTime - mLastTime < clickOnce.value()) {
                Log.w(TAG, "忽略多余的点击事件");
                return;
            }
            mLastId = v.getId();
            mLastTime = currentTime;

            // 正常执行
            joinPoint.proceed();
        } else {
            Log.e(TAG, "这是不可能的");
        }
    }
}

使用方法

@ClickOnce
private void btnClick(View v) {
    Log.i(TAG, "clicked");
}

运行原理

  • @Aspect 用它声明一个类,表示一个需要执行的切面。
  • @Pointcut 声明一个切点:类似sql语句,负责查询
  • @Before/@After/@Around/...(统称为Advice类型) 声明在切点前、后、中执行切面代码。

切点表达式:

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

image.png

还有哪些方式

  • APT
  • Javassist
  • ASM

详见:www.jianshu.com/p/c40528c8d…