Android 进阶篇之AOP

AOP 大家应该都了解过一点,也就是我们所说的面向切面编程,与之相对应的还有 OOP 面向对象编程、POP 面向过程编程,下面我们就一起学习下在 Android 中 AOP 的环境配置以及怎么使用

环境配置

在项目根目录下的 build.gradle 配置

buildscript {
    repositories {
        jcenter()
        google()
    }
    dependencies {
        ...
        classpath 'org.aspectj:aspectjtools:1.9.2'
        classpath 'org.aspectj:aspectjweaver:1.9.2'
    }
}

然后在 app module 目录下创建 aspectj.gradle,添加如下代码,代码中适配了常见的variant.javaCompile编译警告

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

import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.aspectj:aspectjtools:1.9.2'
    }
}

repositories {
    mavenCentral()
}

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 = null
    if (variant.hasProperty('javaCompileProvider')) {
        TaskProvider provider =  variant.javaCompileProvider
        javaCompile = provider.get()
    } else {
        javaCompile = variant.hasProperty('javaCompiler') ? variant.javaCompiler : 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 module 目录下 build.gradle 中引用

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
}
apply from: "aspectj.gradle"

使用

在 Android 中常见的 AOP 使用有防止按钮多次点击检测登陆状态,方法日志打印等等,下面我们就来简单实现下防止按钮多次点击的功能

首先创建aop包,在其包下分别创建annotationaspect, 接着在annotation包下创建SingleClick注解

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

@Retention有三个值可选

RetentionPolicy.SOURCE: 编译后会被丢弃。
RetentionPolicy.CLASS:  编译后会被保留,但不会在运行的VM中保留。默认值
RetentionPolicy.RUNTIME:编译后会被保留,也会在运行的VM中保留。

@Target有如下值可选

ElementType.TYPE : 类型
ElementType.FIELD: 字段,包括属性的支持字段。
ElementType.METHOD: 方法
ElementType.PARAMETER: 参数
ElementType.CONSTRUCTOR: 构造函数
ElementType.LOCAL_VARIABLE: 局部变量
ElementType.ANNOTATION_TYPE: 注解类型
ElementType.PACKAGE: 包名
ElementType.TYPE_PARAMETER: 参数 java1.8开始
ElementType.TYPE_USE: 使用的类型 java1.8开始

aspect包下创建SingleClickAspect类,添加如下代码

@Aspect
public class SingleClickAspect {

    private static int MIN_CLICK_DELAY_TIME = 600;
    private static long lastClickTime = 0L;

    //@Pointcut来标识所要寻找的切点,就是我们定义的@SingleClick注解
    @Pointcut("execution(@com.soaic.helloaspect.aop.annotation.SingleClick * *(..))")//方法切入点
    private void methodAnnotated() {}

    /**
     * joinPoint.proceed() 执行注解所标识的代码
     * @After 可以在方法前插入代码
     * @Before 可以在方法后插入代码
     * @Around 可以在方法前后各插入代码
     */
    @Around("methodAnnotated()")
    private void aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable{
        //获取系统当前时间
        long currentTime = Calendar.getInstance().getTimeInMillis();
        //当前时间-上次记录时间>过滤的时间 过滤掉600毫秒内的连续点击
        //表示该方法可以执行
        if (currentTime - lastClickTime > MIN_CLICK_DELAY_TIME) {
            //将刚进入方法的时间赋值给上次点击时间
            lastClickTime = currentTime;
            //执行原方法
            joinPoint.proceed();
        }
    }

}

最后在按钮点击事件的方法里面声明@SingleClick注解就可以了,使用起来是不是特别方便

@SingleClick
private void login() {
	...
}

在kotlin环境下配置

如果按照上面环境配置在 kotlin 里是不起作用的, 我们这里使用 github 开源的一个插件
GitHub地址: https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx
使用如下:

同样是在项目根目录下的 build.gradle 配置

buildscript {
    ext.kotlin_version = '1.3.41'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        ...
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4'
        classpath 'org.aspectj:aspectjtools:1.9.2'
    }
}

然后在 app module 下的 build.gradle 中配置

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'android-aspectjx'
//或者这样也可以
//apply plugin: 'com.hujiang.android-aspectjx'

android {
    ...

    aspectjx {
        //排除所有package路径中包含`android.support`的class文件及库(jar文件)
        exclude 'android.support'
    }
}

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

至此在 kotlin 下的环境配置就完成了。使用按照上面即可,感谢大家,我们下篇见~

你可能感兴趣的:(Android,学习)