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面向切面编程详解,超实用!