Android中Aop和Apt有什么区别?

什么是Aop?

AOP指的是:面向切面编程(Aspect-Oriented Programming)。如果说,OOP如果是把问题划分到单个模块的话,那么AOP就是把涉及到众多模块的某一类问题进行统一管理。

代表框架:

  • Hugo(Jake Wharton)
  • SSH
  • SpringMVC

Android 中应用

  • 日志
  • 持久化
  • 性能监控
  • 数据校验
  • 缓存
  • 按钮防抖
  • 其他更多

Android AOP就是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率

使用姿势

在Java中使用aop编程需要用到AspectJ切面框架,AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

1.在build.gradle文件中引入AspectJ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
pply plugin: 'com.android.application'
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
android {
...
}

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;
}
}
}
}


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

2.创建注解类

1
2
3
4
5
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface SingleClick {
long clickIntervals() default 800;
}

3.创建切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
* 添加切面注解
*/
@Aspect
public class AopTest {
private static final String TAG = "linhaojian";
/**
* 定义切入点(定义那些类或者方法需要改变)
*/
@Pointcut("execution(* com.lhj.test_apt..*ck(..))")
public void pointcut1() {
}
/**
* 使用注解方式,定义注解
*/
@Pointcut("execution(@com.lhj.test_apt.DebugLog * *ck(..))")
public void pointcut() {
}
/**
* 前置通知,切点之前执行
* @param point
*/
@Before("pointcut()")
public void logBefore(JoinPoint point){
Log.e(TAG,"logBefore");
}
/**
* 环绕通知,切点前后执行
* @param joinPoint
* @throws Throwable
*/
@Around("pointcut()")
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Log.e(TAG,"logAround");
// 1.执行切点函数(如果不调用该方法,切点函数不被执行)
joinPoint.proceed();
}
/**
* 后置通知,切点之后执行
* @throws Throwable
*/
@After("pointcut()")
public void logAfter(JoinPoint point){
Log.e(TAG,"logAfter");
}
/**
* 返回通知,切点方法返回结果之后执行
* @throws Throwable
*/
@AfterReturning("pointcut()")
public void logAfterReturning(JoinPoint point, Object returnValue){
Log.e(TAG,"logAfterReturning ");
}
/**
* 异常通知,切点抛出异常时执行
* @throws Throwable
*/
@AfterThrowing(value = "pointcut()",throwing = "ex")
public void logAfterThrowing(Throwable ex){
Log.e(TAG,"logAfterThrowing : "+ex.getMessage());
}
}

4.使用

1
2
3
4
5
@SingleClick(clickIntervals = 2000)
@Override
public void onClick(View v) {
Toast.makeText(this, "1", Toast.LENGTH_SHORT).show();
}

难点:
AspectJ语法比较多,但是掌握几个简单常用的,就能实现绝大多数切片,完全兼容Java(纯Java语言开发,然后使用AspectJ注解,简称@AspectJ。)

优点:
AspectJ除了hook之外,AspectJ还可以为目标类添加变量,接口。另外,AspectJ也有抽象,继承等各种更高级的玩法。它能够在编译期间直接修改源代码生成class,强大的团战切入功能,指哪打哪,鞭辟入里。有了此神器,编程亦如庖丁解牛,游刃而有余。

什么是Apt?

APT(Annotation Processing Tool 的简称),可以在代码编译期解析注解,并且生成新的 Java 文件,减少手动的代码输入

代表框架:

  • DataBinding
  • Dagger2
  • ButterKnife
  • EventBus3
  • DBFlow
  • AndroidAnnotation

使用姿势

1,在android工程中,创建一个java的Module,写一个类继承AbstractProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@AutoService(Processor.class) // javax.annotation.processing.IProcessor 
@SupportedSourceVersion(SourceVersion.RELEASE_7) //java
@SupportedAnnotationTypes({ // 标注注解处理器支持的注解类型
"com.annotation.SingleDelegate",
"com.annotation.MultiDelegate"
})
public class AnnotationProcessor extends AbstractProcessor {

public static final String PACKAGE = "com.poet.delegate";
public static final String CLASS_DESC = "From poet compiler";

public Filer filer; //文件相关的辅助类
public Elements elements; //元素相关的辅助类
public Messager messager; //日志相关的辅助类
public Types types;

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
filer = processingEnv.getFiler();
elements = processingEnv.getElementUtils();
messager = processingEnv.getMessager();
types = processingEnv.getTypeUtils();

new SingleDelegateProcessor().process(set, roundEnvironment, this);
new MultiDelegateProcessor().process(set, roundEnvironment, this);

return true;
}
}

2,重写AbstractProcessor类中的process方法,处理我们自定义的注解,生成代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class SingleDelegateProcessor implements IProcessor {  
@Override
public void process(Set<? extends TypeElement> set, RoundEnvironment roundEnv,
AnnotationProcessor abstractProcessor) {
// 查询注解是否存在
Set<? extends Element> elementSet =
roundEnv.getElementsAnnotatedWith(SingleDelegate.class);
Set<TypeElement> typeElementSet = ElementFilter.typesIn(elementSet);
if (typeElementSet == null || typeElementSet.isEmpty()) {
return;
}
// 循环处理注解
for (TypeElement typeElement : typeElementSet) {
if (!(typeElement.getKind() == ElementKind.INTERFACE)) { // 只处理接口类型
continue;
}

// 处理 SingleDelegate,只处理 annotation.classNameImpl() 不为空的注解
SingleDelegate annotation = typeElement.getAnnotation(SingleDelegate.class);
if ("".equals(annotation.classNameImpl())) {
continue;
}
Delegate delegate = annotation.delegate();

// 添加构造器
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC);

// 创建类名相关 class builder
TypeSpec.Builder builder =
ProcessUtils.createTypeSpecBuilder(typeElement, annotation.classNameImpl());

// 处理 delegate
builder = ProcessUtils.processDelegate(typeElement, builder,
constructorBuilder, delegate);

// 检查是否继承其它接口
builder = processSuperSingleDelegate(abstractProcessor, builder, constructorBuilder, typeElement);

// 完成构造器
builder.addMethod(constructorBuilder.build());

// 创建 JavaFile
JavaFile javaFile = JavaFile.builder(AnnotationProcessor.PACKAGE, builder.build()).build();
try {
javaFile.writeTo(abstractProcessor.filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}

3,在项目Gradle中添加 annotationProcessor project 引用

1
2
compile project(':apt-delegate-annotation')  
annotationProcessor project(':apt-delegate-compiler')

4,如果有自定义注解的话,创建一个java的Module,专门放入自定义注解。项目与apt Module都需引用自定义注解Module

4-1,主工程:

1
2
compile project(':apt-delegate-annotation')  
annotationProcessor project(':apt-delegate-compiler')

4-2,apt Module:

1
2
3
compile project(':apt-delegate-annotation')  
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.4.0'

5,生成的源代码在build/generated/source/apt下可以看到

难点

就apt本身来说没有任何难点可言,难点一在于设计模式和解耦思想的灵活应用,二在与代码生成的繁琐,你可以手动字符串拼接,当然有更高级的玩法用squareup的javapoet,用建造者的模式构建出任何你想要的源代码

优点

它的强大之处无需多言,看代表框架的源码,你可以学到很多新姿势。总的一句话:它可以做任何你不想做的繁杂的工作,它可以帮你写任何你不想重复代码。懒人福利,老司机必备神技,可以提高车速,让你以任何姿势漂移。它可以生成任何源代码供你在任何地方使用,就像剑客的剑,快疾如风,无所不及

Aop和Apt对比

如图所示:

本帖附件

点击下载

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

0%