AOP面向切面编程(Aspect-Oriented Programming)。如果说,OOP如果是把问题划分到单个模块的话,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。Android AOP就是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。根据切面代码所产生的注入代码时间不同可以分为下面三个大的方向:
最上层的编译时注解(APT),应该是这其中我们最常见到的了,难度也最低。在我们的手写注解处理器那三篇博客中已经详细介绍过了,简单来说就是定义编译期的注解,再通过继承Proccesor实现代码生成逻辑,实现了编译期生成代码的逻辑。请看
Android组件化系列之静态注解手写注解、注解处理器、及注解工作流程(上)
AutoService注解无法生成META-INF文件
Android组件化系列之动态注解手写注解、注解处理器、及注解工作流程(下)
当我们需要在某个方法运行前和运行后做一些处理时,便可使用AOP技术。具体有:
- 统计埋点
- 日志打印/打点
- 数据校验
- 行为拦截
- 性能监控
- 动态权限控制
AspectJ支持编译期和加载时代码注入,其实就是一个代码生成工具。今天我们来试用下在class字节码框架上对代码的注入功能,为了快速上手AspectJ,我们这里列举一个最简单的实例来演示下这玩意儿是怎么用的,简单的结构能帮助我们快速理解他的使用规则。实例就是统计下我们关注的函数的执行时间的长度。
步骤一:添加依赖,在根工程和APP工程中分别都添加依赖:implementation 'org.aspectj:aspectjrt:1.9.4'
为了使AspectJ生效,还需要添加下面这段配置代码
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;
}
//执行到这里,使 aspectj 配置生效
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.9",
"-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;
}
}
}
}
步骤二:添加一个注解,作用一是在使用时进行标记,二是在框架处理时进行识别:
/**
* 用来表示性能监控
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BehaviorTrace {
String value();
}
步骤三:编写切面代码,使用@Aspect注解供库识别,使用@Pointcut进行切入点的声明,@Around就是我们的切面表示在切入点的前后都需要执行,由于我们统计的是函数的执行时间长短,因此切面是切入点的前后执行
@Aspect
public class BehaviorTraceAspect {
//定义切面的规则
//1、就再原来的应用中那些注解的地方放到当前切面进行处理
//execution(注解名 注解用的地方)
@Pointcut("execution(@com.dn_alan.myapplication.annotation.BehaviorTrace * *(..))")
public void methodAnnottatedWithBehaviorTrace() {
}
//2、对进入切面的内容如何处理
//@Before 在切入点之前运行
// @After("methodAnnottatedWithBehaviorTrace()")
//@Around 在切入点前后都运行
@Around("methodAnnottatedWithBehaviorTrace()")
public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String className = methodSignature.getDeclaringType().getSimpleName();
String methodName = methodSignature.getName();
String value = methodSignature.getMethod().getAnnotation(BehaviorTrace.class).value();
long begin = System.currentTimeMillis();
Object result = joinPoint.proceed();
SystemClock.sleep(500);
long duration = System.currentTimeMillis() - begin;
Log.d("alan", String.format("%s功能:%s类的%s方法执行了,用时%d ms",
value, className, methodName, duration));
return result;
}
}
步骤四:最后使用注解在我们关注方法上生效就完成了,是不是很简单
//语音消息
@BehaviorTrace("语音消息")
public void mAudio(View view) {
}
最后测试结果:
语音消息功能:MainActivity类的mAudio方法执行了,用时504 ms
视频通话功能:MainActivity类的mVideo方法执行了,用时502 ms
发表说说功能:MainActivity类的saySomething方法执行了,用时501 ms
上面是整体步骤,完整的项目在:
https://github.com/buder-cp/DesignPattern/tree/master/buder_DN_AOP/DN_AOP