面向切面编程:aspectJ在Android中的使用

一、 简介和使用场景

aspectJ 是eclipse社区中的一个开源工具,可以对java编程语言面向切面进行无缝拓展、完全兼容java语言,它可以对关注切点进行优雅处理,比如错误检查与处理、性能优化、监视与日志记录等场景。说到面向切面编程,大家可能会联想到著名的Spring AOP,Spring AOP是基于动态代理模式实现的。代理模式分为静态代理和动态代理,静态代理在编译期修改代码将指定对象注入到代码中,拓展性差、耦合性强;动态代理在运行时通过反射动态获取对象并调用其方法,效率低、耦合性强,Spring AOP通过IOC依赖注入技术一定程度上解决了耦合的问题,但是执行效率没有得到解决;aspectJ在编译期修改代码,并且和业务逻辑代码分离,耦合性强,并且不影响运行时的效率。JakeWharton大神的函数耗时监控开源项目hugo 就使用了aspectJ技术。

二 、基本概念

2.0 注解

手动实现bindview一文用到了注解,现在越来越觉得注解用处真大,注解的具体使用可以参考我之前的文章 注解 ,这里我创建一个运行时注解。

//CheckNet.java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CheckNet {
}

2.1 Aspect 切面

切面是切入点和通知的集合,@Aspect 用它声明一个类,表示一个需要执行的切面,比如我这里创建一个类,并在类的顶部添加@Aspect注解。

//MyAspect.java
@Aspect
public class MyAspect {
}

2.2 PointCut 切入点

切入点又称触发条件,是指那些通过使用一些特定的表达式过滤出来的想要切入Advice的连接点,@Pointcut 声明一个切点。

面向切面编程:aspectJ在Android中的使用

 

 

 

 

 

 

 

 

 

  

 

 这里我想做个网络判断的逻辑

//MyAspect.java
@Pointcut("execution(@com.shan.aspectjapp.CheckNet * *(..))")
public void checkNetAction(){

}

 在Pointcut这里,我使用了execution,也就是以方法执行时为切点,触发Aspect类。而execution里面的字符串是触发条件,也是具体的切点。我来解释一下参数的构成。"execution(@com.shan.aspectjapp.CheckNet * *(..))"这个条件是所有加了CheckNet 注解的方法或属性都会是切点,范围比较广。

  • * *:表示是任意包名
  • ..:表示任意类型任意多个参数

2.3 Advice 通知

通知是向切点中注入的代码实现方法,@Before/@After/@Around/...(统称为Advice类型) 声明在切点前、后、中执行切面代码,详细见下表:

Before

前置通知, 在目标执行之前执行通知

After

后置通知, 目标执行后执行通知

Around

环绕通知, 在目标执行中执行通知, 控制目标执行时机

AfterReturning

后置返回通知, 目标返回时执行通知

AfterThrowing

异常通知, 目标抛出异常时执行通知

这里使用@Around在目标代码执行时去判断是否有网络,如果没网络则不再执行目标方法,否则执行目标方法

@Around("checkNetAction()")
public Object checkNet(ProceedingJoinPoint joinPoint) throws Throwable {
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    CheckNet checkNet = signature.getMethod().getAnnotation(CheckNet.class);
    LogUtil.d("checkNet,"+checkNet);
    if (checkNet!=null) { 
        if (!isNetworkAvailable()) {
            Toast.makeText(MyApp.getContext(),"please check your network",Toast.LENGTH_SHORT).show();
            return null; //无网络则目标方法不再继续执行
        }
    }
    return joinPoint.proceed(); //有网络则继续执行目标方法
}

2.4 Joint Point 连接点

所有的目标方法都是连接点.

2.5 Weaving 编织

主要是在编译期使用AJC将切面的代码注入到目标中, 并生成出代码混合过的.class的过程.

三、 android中使用

3.1 下载并安装aspectJ

可以在aspectJ官网上下载aspectj.jar,然后双机一路next直到安装成功,本人这里下载的是1.9.6版本,安装后在安装目录 找到aspectjrt.jar后面在android工程中会用到这个jar。

 

面向切面编程:aspectJ在Android中的使用

面向切面编程:aspectJ在Android中的使用

 

3.2 配置android依赖

首先工程根目录的build.gradle中添加aspectJ依赖

//build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ……
    dependencies {
        classpath "com.android.tools.build:gradle:4.2.1"
        //重点是下面两行
        classpath 'org.aspectj:aspectjtools:1.9.6'
        classpath 'org.aspectj:aspectjweaver:1.9.6'
    }
}

 

然后在主module的build.gradle添加依赖和环境

//build.gradle
plugins {
    id 'com.android.application'
}
//重点下面三行
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;
            }
        }
    }
}

最后将aspectjrt.jar放到libs目录并添加依赖

3.3 使用

然后在activity中点击登陆,如果无网络则下面的"we will surf the Internet"提示就不会走到,而会提示MyAspect.java中的"please check your network";如果有网络则会提示"we will surf the Internet".

//MainActivity.java
public class MainActivity extends AppCompatActivity {

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


    @CheckNet //这就是面向切面编程了
    public void login(View view) {
        Toast.makeText(this,"we will surf the Internet",Toast.LENGTH_SHORT).show();
    }
}

 参考:

安卓架构师必备之Android AOP面向切面编程详解,超实用!

 

上一篇:SpringBoot AspectJ做AOP日志管理


下一篇:springAOP jdk动态代理学习笔记