Android AOP 编程实践 javapoet + autoService
什么是AOP?
AOP面向切面编程,就是在代码预编译阶段,在不修改源代码的情况下,给程序添加某一功能。
像成熟的框架,ARouter,ButterKnife等也都使用了这个技术。任何技术的出现都有其实际应用场景,为了解决某一方面的痛点。AOP的出现让某些功能组件的封装更加解耦,使用者能够更加的方便的使用组件里的功能。
拿ButterKnife举例,我们原生开发,以前经常写很多findViewById的代码,显然这类代码写起来很繁琐,且容易出错(id和view有时候没对上)。而AOP可以有效避免这些问题。
比如我们可以通过在预编译的阶段解析注解,然后生成对应的java文件,该java文件封装了findviewbyid的方法,实现view和id的动态绑定。这样就非常有效减少了后期的编写代码的工作量,可以快速实现view和id的绑定操作。
AOP编程思路:
一.定义注解:自定义注解参考下面例子即可,default表示该字段不是必传字段,下面例子的自定义注解在使用的时候必须传入name。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Laucher {String name();boolean async() default false;int priority() default -1;String[] preTasks() default {};
}
元注解 说明 @Target 定义该自定义注解类型。 @Retention 这个注解的的存活时间 @Document 表明注解可以被javadoc此类的工具文档化 @Inherited 是否允许子类继承该注解,默认为false@Target 类型 说明 ElementType.TYPE 接口、类、枚举、注解 ElementType.FIELD 字段、枚举的常量 ElementType.METHOD 方法 ElementType.PARAMETER 方法参数 ElementType.CONSTRUCTOR 构造函数 ElementType.LOCAL_VARIABLE 局部变量 ElementType.ANNOTATION_TYPE 注解 ElementType.PACKAGE 包 @Retention 说明 RetentionPolicy.SOURCE 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃 RetentionPolicy.CLASS 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期 RetentionPolicy.RUNTIME 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
二.编译阶段解析注解:创建一个java-library(不是androd library),实现自定义Processor,注册Processor。引入auto-service可以实现自动注册。rebuild之后,在build目录会生成javax.annotation.processing.Processor文件,这个文件能看到你注册的Processor。
dependencies {implementation project(path: ':xlauncher-annotation')implementation 'com.squareup:javapoet:1.8.0'annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'compileOnly 'com.google.auto.service:auto-service-annotations:1.0-rc7'
}
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.azx.xlauncher_annotation.Laucher"})
@AutoService(Processor.class)
public class LauncherProcessor extends AbstractProcessor {private Map<String, LauncherTask> taskMap = new HashMap<>();private String inputParamsName = "taskMap";private Filer filer;private Writer docWriter;private Logger logger;Elements elementUtils;@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);logger = new Logger(processingEnv.getMessager());filer = processingEnv.getFiler();elementUtils = processingEnv.getElementUtils();try {docWriter = filer.createResource(StandardLocation.SOURCE_OUTPUT,PACKAGE_OF_GENERATE_DOCS,PACKAGE_OF_GENERATE_DOCS+".json").openWriter();} catch (IOException e) {logger.error(e);}}@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {Set<? extends Element> launcherElements = roundEnvironment.getElementsAnnotatedWith(Laucher.class);parseElements(launcherElements);try {createJavaDoc(launcherElements);} catch (IOException e) {e.printStackTrace();}return true;}private void createJavaDoc(Set<? extends Element> elements) throws IOException {ParameterizedTypeName mapTaskLauncher = ParameterizedTypeName.get(ClassName.get(HashMap.class),ClassName.get(String.class),ClassName.get(LauncherTask.class));ParameterSpec paramSpec = ParameterSpec.builder(mapTaskLauncher, inputParamsName).build();MethodSpec.Builder method = MethodSpec.methodBuilder(METHOD_NAME).addAnnotation(Override.class).addModifiers(PUBLIC).addParameter(paramSpec);addCodeIntoMethod(method, elements);JavaFile.builder(PACKAGE_OF_GENERATE_FILE,TypeSpec.classBuilder(CLASS_NAME) //class文件名字.addModifiers(Modifier.PUBLIC) //公有方法.addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITASK_MANAGER))).addJavadoc("@Launcher注解自动生成的java文件").addMethod(method.build()).build()).build().writeTo(filer);}private void addCodeIntoMethod(MethodSpec.Builder method, Set<? extends Element> elements) {ClassName classTask = ClassName.get(LauncherTask.class);Set<String> keys = taskMap.keySet();for (Element item : elements) {Laucher annotation = item.getAnnotation(Laucher.class);String key = annotation.name();logger.info(key + " " + ClassName.get((TypeElement)item));method.addStatement("$T.build($S, $T.class, " + taskMap.get(key).getPriority() + "," + taskMap.get(key).isAsync() + ", " + createListMethod(taskMap.get(key).getPreTaskList()) + ")",classTask,taskMap.get(key).getTaskName(),ClassName.get((TypeElement)item));}}private String createListMethod(List<String> list) {List<String> test = new ArrayList() {{add("a");}};StringBuilder builder = new StringBuilder();for (String item : list) {builder.append("add(\"" + item + "\"); ");}return "new java.util.ArrayList<String>(){{" + builder.toString() + "}}";}private void parseElements(Set<? extends Element> elements) {taskMap.clear();for (Element item : elements) {LauncherTask launcherTask = new LauncherTask();Laucher annotation = item.getAnnotation(Laucher.class);launcherTask.setTaskName(annotation.name());launcherTask.setAsync(annotation.async());launcherTask.setPriority(annotation.priority());launcherTask.setPreTaskList(Arrays.asList(annotation.preTasks()));taskMap.put(launcherTask.getTaskName(), launcherTask);}}
}
三.生成java文件:
我们可以看上面的LauncherProcessor类的createJavaCode的方法,里面的代码是整个生成java文件的逻辑。
我们先通过parseElements方法获取使用到Laucher注解的Elements,然后解析Laucher注解内部的参数。
接下来我们一步步阅读代码
ParameterizedTypeName mapTaskLauncher = ParameterizedTypeName.get(ClassName.get(HashMap.class),ClassName.get(String.class),ClassName.get(LauncherTask.class));
//这个代码实际上就是创建一个参数类型,对应的代码是HashMap<String, LauncherTask>private String inputParamsName = "taskMap"; ParameterSpec paramSpec = ParameterSpec.builder(mapTaskLauncher, inputParamsName).build();
//这个代码就是生成一个参数,对应的java代码是 HashMap<String, LauncherTask> taskMap//下面的代码是构建一个方法对象,public属性,入参是HashMap<String, LauncherTask> taskMap,带有Override注解
MethodSpec.Builder method = MethodSpec.methodBuilder(METHOD_NAME).addAnnotation(Override.class).addModifiers(PUBLIC).addParameter(paramSpec);addCodeIntoMethod(method, elements);
private void addCodeIntoMethod(MethodSpec.Builder method, Set<? extends Element> elements) {ClassName classTask = ClassName.get(LauncherTask.class);Set<String> keys = taskMap.keySet();for (Element item : elements) {Laucher annotation = item.getAnnotation(Laucher.class);String key = annotation.name();logger.info(key + " " + ClassName.get((TypeElement)item));method.addStatement("$T.build($S, $T.class, " + taskMap.get(key).getPriority() + "," + taskMap.get(key).isAsync() + ", " + createListMethod(taskMap.get(key).getPreTaskList()) + ")",classTask,taskMap.get(key).getTaskName(),ClassName.get((TypeElement)item));}}
//在方法体里面插入代码。
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,TypeSpec.classBuilder(CLASS_NAME) //class文件名字.addModifiers(Modifier.PUBLIC) //公有方法.addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITASK_MANAGER))).addJavadoc("@Launcher注解自动生成的java文件").addMethod(method.build()).build()).build().writeTo(filer);
构建class,并把方法插入,通过javadoc接口加入类的注解,并设置class实现的接口等,然后写入文件。
经过上面代码的处理,我们可以在build目录下面找到生成后的java文件,下图就是生成代码的结果。
四.可能遇到的问题:
1.Processor,init方法没走!
可能原因1:配置问题,jdk没统一,如果设置的jdk-8,结果annotation和compile的lbrary设置了java-7,其次也有可能是androd studio的jdk设置不是jdk-8。
可能原因2:SupportedAnnotationTypes设置错误,在log里面警告没找到对应注解。
2.Processor,init方法走了,但是process方法不执行。
1.java定义的注解用在了kotlin文件上面
2.某个模块使用了该注解,也implement了annotation的lib,但是没导入有自定义Processor的compile的lib。
Android AOP 编程实践 javapoet + autoService相关推荐
- Android AOP编程(五)——Gradle插件+TransformAPI+字节码插桩实战
开篇 在前面几篇博文中,我记录了Android AOP编程的一些基础知识,包括Gradle插件的开发.TransformAPI的使用,以及一些操作字节码的工具如AspectJ,Javassist和AS ...
- Android切面编程(AOP)详解
文章目录 什么是AOP AOP使用场景 AspectJ AspectJ基本概念 AspectJ的使用 引入AspectJ 创建切面AspectJ 添加注解 什么是AOP AOP是Aspect Orie ...
- Android AOP 面向切面编程
一.AOP即面向切面编程 AOP 是 Aspect Oriented Programming 的缩写,译为面向切面编程.用我们最常用的 OOP 来对比理解: 纵向关系 OOP,横向角度 AOP 举个例 ...
- Android——面向AOP编程(二) Xerath 开源AOP框架
承接上文Android--面向AOP编程(一)https://pumpkin.blog.csdn.net/article/details/119849325https://pumpkin.blog.c ...
- Android AOP之字节码插桩
背景 本篇文章基于<网易乐得无埋点数据收集SDK>总结而成,关于网易乐得无埋点数据采集SDK的功能介绍以及技术总结后续会有文章进行阐述,本篇单讲SDK中用到的Android端AOP的实 ...
- 【编程实践】Google Guava 极简教程
前言 Guava 工程包含了若干被 Google 的 Java 项目广泛依赖 的核心库,我们希望通过此文档为 Guava 中最流行和最强大的功能,提供更具可读性和解释性的说明. 适用人群 本教程是基础 ...
- Java-gt;Android并发编程引气入门篇
Android的并发编程,即多线程开发,而Android的多线程开发模型也是源于Java中的多线程模型. 所以本篇也会先讲一些Java中的多线程理念,再讲解具体涉及的类,最后深入Android中的并发 ...
- Android Aop预研
预研目的 公司要求,希望我整理一下项目中的log日志,由于当前项目已经很庞大,包含多个自主开发的library,并且由多个开发人员共同完成.不同的同事,打log的方式都不一样,没有同一个的格式,因此我 ...
- Android高级编程.
Android高级编程. 2011新版Ja va教程.ppt: http://www.t00y.com/file/60254549 andbook英文版本.pdf: http://www.t00y.c ...
最新文章
- java解数独_java解数独
- python访问数据库日志文件_python利用inotify实现把nginx日志实时写入数据库
- 为CentOS 6 配置本地YUM源
- UVa 1354 天平难题 枚举二叉树
- sql服务器默认密码_搭建一个DNS服务器,轻松实现域名解析内容分发,访问速度提高N倍...
- pca百分比取多少比较好_母亲节给妈妈发多少红包比较好合适 母亲节红包吉利数字含义...
- UTF-8 's format
- Centos 7安装报错:Warning:dracut-initqueue timeout - starting timeout scripts
- java关键字值transient
- XAMPP 使用教程
- amd服务器开启虚拟化技术,在AMD平台上实现KVM虚拟化技术
- 高一计算机算法教案,教科版 高一信息技术 必修1 第四单元 4.2 数值计算 教案...
- java wsimport https_java – 当服务器需要客户端证书时如何使用wsimport?
- 学习markdown
- 洛谷 P1338 末日的传说
- linux下git和github搭建使用教程
- Excel if函数用法
- 数据库“交叉表”查询的实现
- 什么是依赖注入和控制反转
- Java第三天笔记01——流程控制语句