aspectJ 是eclipse社区中的一个开源工具,可以对java编程语言面向切面进行无缝拓展、完全兼容java语言,它可以对关注切点进行优雅处理,比如错误检查与处理、性能优化、监视与日志记录等场景。说到面向切面编程,大家可能会联想到著名的Spring AOP,Spring AOP是基于动态代理模式实现的。代理模式分为静态代理和动态代理,静态代理在编译期修改代码将指定对象注入到代码中,拓展性差、耦合性强;动态代理在运行时通过反射动态获取对象并调用其方法,效率低、耦合性强,Spring AOP通过IOC依赖注入技术一定程度上解决了耦合的问题,但是执行效率没有得到解决;aspectJ在编译期修改代码,并且和业务逻辑代码分离,耦合性强,并且不影响运行时的效率。JakeWharton大神的函数耗时监控开源项目hugo 就使用了aspectJ技术。
在手动实现bindview一文用到了注解,现在越来越觉得注解用处真大,注解的具体使用可以参考我之前的文章 注解 ,这里我创建一个运行时注解。
//CheckNet.java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CheckNet { }
切面是切入点和通知的集合,@Aspect 用它声明一个类,表示一个需要执行的切面,比如我这里创建一个类,并在类的顶部添加@Aspect注解。
//MyAspect.java @Aspect public class MyAspect { }
切入点又称触发条件,是指那些通过使用一些特定的表达式过滤出来的想要切入Advice的连接点,@Pointcut 声明一个切点。
这里我想做个网络判断的逻辑
//MyAspect.java @Pointcut("execution(@com.shan.aspectjapp.CheckNet * *(..))") public void checkNetAction(){ }
在Pointcut这里,我使用了execution,也就是以方法执行时为切点,触发Aspect类。而execution里面的字符串是触发条件,也是具体的切点。我来解释一下参数的构成。"execution(@com.shan.aspectjapp.CheckNet * *(..))"这个条件是所有加了CheckNet 注解的方法或属性都会是切点,范围比较广。
通知是向切点中注入的代码实现方法,@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(); //有网络则继续执行目标方法 }
所有的目标方法都是连接点.
主要是在编译期使用AJC将切面的代码注入到目标中, 并生成出代码混合过的.class的过程.
可以在aspectJ官网上下载aspectj.jar,然后双机一路next直到安装成功,本人这里下载的是1.9.6版本,安装后在安装目录 找到aspectjrt.jar后面在android工程中会用到这个jar。
首先工程根目录的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目录并添加依赖
然后在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面向切面编程详解,超实用!