基本环境搭建(auto&javapoet)

一、API采取背景,举例

主要为了解决客户端MVP架构中,V层和P层生命周期不同步时,生成空View保护性逻辑。

之前需要手写空View的代码,现在通过注解配置可自动生成,同理于黄油刀的@ BindView 注解。

通过配置该注解,在编译期 (compileDebugJavaWithJavac)会自动生成Java类。

强制实现抽象方法getEmptyView(),点击emptyView(接口实例化的对象)

如果Iview需要添加新的方法,它的子类需要不断重写新的方法,这样就很麻烦,就可以通过APT解决(编译器生成代码),类似于ButterKnife来做这个框架

这里会引用到当前的Activity,他把我们当前的实例保存下来

如何保存的呐,就是通过

参考butterknife源码

利用javapoet语法搭建编译环境

通过注解的方法,在编译器里来修饰相应的一个类(或接口)

新建一个Module,这次不选Android Library(有自己的库和gradle脚本,可以存放视频和图片,体积过大) ,而是选Java Library(就是编译期生成的源码)更轻量级就行了

模仿ViewInject

@Retention(RUNTIME)  //运行时 注解
@Target(TYPE) // 类 接口 注解
public @interface ViewInject {int mainlayoutid()  default  -1;
}

参考文档 Android连载课程

二、APT的使用

1    新建两个Java Lib

仓库依赖来源 https://github.com/google/auto

第一个annotation  专门存放编译期注解。

第二个 apt 专门存放生成这个Java代码的注解处理器,并在Gradle添加两个外部包和自定义注解依赖。

implementation 'com.squareup:javapoet:1.9.0'
implementation 'com.google.auto.service:auto-service:1.0-rc3'
implementation project(':annotation')

分别是配合apt便捷生成java文件的工具及特定路径下生成配置文件。

2   注解处理器

继承AbstractProcessor并复写process方法,同时添加下图三个注解。

如果apt这个Module是Android Library是引用不到AbstractProcessor这个抽象的注解处理器,只有Java Library可以

@AutoService(Processor.class) //生成;APT的入口
@SupportedAnnotationTypes({"com.web.god.annotation.MvpEmptyViewFactory"})
public class MvpProccesor extends AbstractProcessor {public Filer mFiler;@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {mFiler = processingEnv.getFiler();new MvpEmptyViewProcesor().process(roundEnv,this);return true;}
}

@AutoService(Processor.class)注解,这个注解会自动在指定路径下生成一个配置文件:

@SupportedAnnotationTypes注解,配置这个类所要处理的注解类型。(传入String类型参数,格式为:包名+类名);

@SupportedSourceVersion(SourceVersion.RELEASE_8)注解,JDK版本

3   集成APT

项目Gradle 文件 依赖上述两个Java Lib。

implementation project(':annotation')

kapt/ annotationProcessor project(':apt')   (要看项目中是否有用 apply plugin: 'kotlin-kapt')

MvpEmptyViewProcessor  MVP空View Java代码生成器

这里的代码主要是用javapoet 这个库的语法通过该注解拿到的信息生成Java类。

参考 https://github.com/square/javapoet

   

拿到生成相应代码逻辑

大致逻辑:

  1. 获取被编译期注解修饰的类信息。
  2. 获取该类及父类结构里方法、参数、返回值信息。
  3. 根据这些信息生成该类的匿名对象。

写入文件时,传入的包名

通过常量获取类名

/***  把这个类里面的具体实现,生成逻辑用到javapoet*/
public class MvpEmptyViewProcesor {String CLASS_NAME = "MvpEmptyViewFactory";/**** @param roundEnv 编译环境* @param processor 继承来自AbstractProcessor;抽象编程*/public void process(RoundEnvironment roundEnv, MvpProccesor processor) {try {//创建Class名字,类的修饰,增加类下面的方法(构造者设计模式)TypeSpec.Builder tb = TypeSpec.classBuilder(CLASS_NAME).addModifiers(PUBLIC, FINAL).addJavadoc("empty view by apt");//类下面的具体方法内容参数,内部逻辑;方法名=create;参数=mClassMethodSpec.Builder methodBuilder1 = MethodSpec.methodBuilder("create").returns(Object.class).addModifiers(PUBLIC, STATIC).addException(IllegalAccessException.class).addException(InstantiationException.class).addParameter(Class.class, "mClass");List<ClassName> mList = new ArrayList<>();//拼接字符串CodeBlock.Builder blockBuilder = CodeBlock.builder();//beginControlFlow()方法添加一个switch(),这里是“开始”blockBuilder.beginControlFlow(" switch (mClass.getCanonicalName())");//ElementFilter.typesIn()全局获取编译期被MvpEmptyViewFactory修饰所有类的信息for (TypeElement element : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(MvpEmptyViewFactory.class))) {ClassName currentType = ClassName.get(element);if (mList.contains(currentType)){continue; //有类重复获取时,跳过}mList.add(currentType); //如果没有,就把当前现象记录到类List里面//生成本身的方法StringBuilder s = new StringBuilder();//类的信息去获取所有方法的集合List<? extends Element> enclosedElements = element.getEnclosedElements();for (int i = 0; i < enclosedElements.size(); i++) { //获取它本身所有方法的信息if (enclosedElements.get(i) instanceof ExecutableElement) {ExecutableElementBean elementBean = ExecutableElementParseUtil.parseElement((ExecutableElement) enclosedElements.get(i));//通过拼接字符串信息去做s.append("@Override ").append("public").append(" ").append(elementBean.returnType).append(" ").append(elementBean.methordName).append("(").append(elementBean.params).append(")").append(String.format("{%s}\n", ExecutableElementParseUtil.getReturnType(elementBean)));}}//生成父类的接口方法(这是一个递归的操作),就是下面这个方法getSuperFun(element, s);//addStatement()添加一段话,通过占位符方式,case $S对应element.toString(),$T对应currentType,$L对应s(StringBuilder把所有字符串拼接到一起)blockBuilder.addStatement("case $S : \n return  new $T(){ \n$L }", element.toString(), currentType, s);}blockBuilder.addStatement("default: return null");//switch()“结束”blockBuilder.endControlFlow();//上面MethodSpec.methodBuilder("create"),create方法,它有个addCode的API,把这个switch()语法体加入进去methodBuilder1.addCode(blockBuilder.build());//上面TypeSpec.classBuilder(CLASS_NAME),就是MvpEmptyViewFactory类(@interface),addMethod()把create方法加进去tb.addMethod(methodBuilder1.build());JavaFile javaFile = JavaFile.builder("today.information.mvp", tb.build()).build();//这些类的信息通过writeTo()生成到对应包名路径today.information.mvp下面javaFile.writeTo(processor.mFiler);} catch (Exception e) {e.printStackTrace();}}/*** 递归获取 父类的方法** @param element* @param s*/private void getSuperFun(TypeElement element, StringBuilder s) {List<? extends TypeMirror> interfaces = element.getInterfaces();if (interfaces != null && interfaces.size() > 0) {for (int i = 0; i < interfaces.size(); i++) { //不断去遍历它的父类有哪些方法,包括它的父类的父类TypeMirror typeMirror = interfaces.get(i);if (typeMirror instanceof DeclaredType) {Element element1 = ((DeclaredType) typeMirror).asElement();List<? extends Element> innerElements = element1.getEnclosedElements();for (int j = 0; j < innerElements.size(); j++) {if (innerElements.get(i) instanceof ExecutableElement) {ExecutableElementBean elementBean = ExecutableElementParseUtil.parseElement((ExecutableElement) innerElements.get(j));s.append("@Override ").append("public").append(" ").append(elementBean.returnType).append(" ").append(elementBean.methordName).append("(").append(elementBean.params).append(")").append(String.format("{%s}\n", ExecutableElementParseUtil.getReturnType(elementBean)));}}if (element1 instanceof TypeElement) {getSuperFun((TypeElement) element1, s); //递归}}}}}
}

要生成的效果

注解生成器去遍历循环的作用

第一步:先获取被@MvpEmptyViewFactory注解修饰这个类的所有信息

第二步:然后拿到类的信息通过相应的方法去获取它本身自带的方法以及它父类的所有方法

建立MVP中空指针的保护机制

这样做是为了减少空指针异常,因为P层这个方法里面对View层做了一层弱引用,所以需要判空的

/*** 集成mvp 及 网络请求 快捷方式* 抽象类改成实现getEmptyView()泛型类型的方法*/
//public abstract class BasePresenter<T extends IMvpView> extends BaseMvpPresenter<T>
public  class BasePresenter<T extends IMvpView> extends BaseMvpPresenter<T>{public BasePresenter(T view) {super(view);}public void submitTask(LfTask task) {TaskHelper.submitTask(task,task);}/***  BasePresenter本身是泛型继承IMvpView,所以采取泛型* 通用逻辑来完成P层对APT生成MvpEmptyViewFactory类的使用* 这里使用到了P层对View层的弱引用,会出现空指针异常,需判空* @return 判空方式:try{}catch 未return 判空错误*/@Overrideprotected T getEmptyView() {T t = null;//GenericsUtils工具类获取当前类泛型里面的参数-ClassClass superClassGenricType = GenericsUtils.getSuperClassGenricType(this.getClass(), 0);try {t = (T)MvpEmptyViewFactory.create(superClassGenricType); //强转为泛型T} catch (Exception e) {e.printStackTrace();}/***  这样就可以使生成类MvpEmptyViewFactory生成IMainActivityContract类下@MvpEmptyViewFactory所注解的Iview接口下的所有方法*  同时Iview是继承IMvpView控制层的,可以减少空指针异常,因为P层这个方法里面对View层做了一层弱引用,有try{}catch来进行判空*/return t;}
}

断点调试最重要

因为相对看这种新的API,很难看到,查到的资料都来自国外,中文的资料都比较少,所以最好的方法是通过断点调试了解大概,但是APT是通过编译器去执行一行行代码的,平时打的断点都打不到这里面来!!!解决方案如下:

4   生成及调试

在想要生成的地方用编译期注解进行修饰,然后Rebuild项目。

APT 调试  https://blog.csdn.net/jonch_hzc/article/details/78796592

三、经验总结

  1. 适用场景

逻辑比较简单的场景,如逻辑较为复杂定位问题比较麻烦 其次 会影响编译速度,简单有:

findViewById,new一个对象...

生成的这个类是无法改的,直接打包到apk里面了,log日志也只能放到生成器里面打印

2.建议

可以看看黄油刀ButterKnife的源码,对更深层次的理解APT有很大帮助。

查看接口有多少例子在继承它

注释掉

Android-黄油刀ButterKnife依赖注入源码,APT自动生成代码,利用javapoet语法搭建编译环境,建立MVP中空指针的保护机制相关推荐

  1. Spring源码分析(十)依赖注入源码解析3:DefaultListableBeanFactory#doResolveDependency 真正开始解析依赖项

    4.2 真正开始解析依赖项(最核心方法) org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveD ...

  2. spring源码分析03-spring依赖注入源码解析

    依赖注入流程图: 1. Spring中有几种依赖注入的方式? 1.1手动注入 在XML中定义Bean时,就是手动注入,因为是程序员手动给某个属性指定了值. 下面这种底层是通过set方法进行注入. &l ...

  3. Spring之依赖注入源码解析

    依赖注入底层原理流程图:Spring中Bean的依赖注入原理 | ProcessOn免费在线作图,在线流程图,在线思维导图 | 1.Spring中到底有几种依赖注入方式 手动注入和自动注入 1.手动注 ...

  4. Spring源码分析(十二)autowire和@Autowired 依赖注入源码解析总结

    XML的autowire自动注入 在XML中,我们可以在定义一个Bean时去指定这个Bean的自动注入模式: byType byName constructor default no 比如: < ...

  5. Vue源码学习之生成代码

    执行 以下生成代码 const code = generate(ast, options). generate函数在src/compiler/codegen/index.ts文件中定义为 export ...

  6. bootstrap自动生成html,利用Bootstrap快速搭建个人响应式主页(附演示+源码)-ATtuing...

    1.前言 我们每个程序员都渴望搭建自己的技术博客平台与他人进行交流分享,但使用别人的博客模板没有创意.做网站后台的开发人员可能了解前端,可是自己写一个不错的前端还是很费事的.幸好我们有Bootstra ...

  7. C++版Android实时投屏软件系统源码,安卓手机投屏软件源码,无需root权限

    QtScrcpy QtScrcpy 可以通过 USB / 网络连接Android设备,并进行显示和控制.无需root权限. 同时支持 GNU/Linux ,Windows 和 MacOS 三大主流桌面 ...

  8. ubuntu 14.04.5 编译Android 4.4.4 r1源码(最新)

    本文博客链接:http://blog.csdn.net/qq1084283172/article/details/54426189 吐槽:ubuntu系统真是让人又爱又恨,也有可能是VMware Wo ...

  9. Android Jetpack 组件之 Lifecycle源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

最新文章

  1. 自己动手在 Linux 系统实现一个 everything 程序
  2. Error in exists(x): 第一个参数不对
  3. 视觉系统的演化之旅——视觉器官、光感受器及视觉分子
  4. 2_2 DecorateMode.cpp 装饰者模式
  5. 某瓜数据之sign参数分析
  6. LeetCode141 Linked List Cycle. LeetCode142 Linked List Cycle II
  7. 第六:Pytest中的setup/teardown
  8. Opera R3 将使用新的用户界面
  9. 专业制造计算机电缆,茶陵DJYP2VP2-22计算机电缆专业制造
  10. 物资管理信息系统4 -- 修改密码界面
  11. 在VS2012集成Fortran95(Ftn95)
  12. 劝你别把开源的数据分析项目写在简历上了!!!
  13. SpringBoot 2.X 整合 druid + dynamic-datasource 多数据源方案
  14. 高精度绝对角度传感器应用高速度角度监测
  15. java简繁体互转(附源码和字典)
  16. 一文搞懂移动端单位em、rem、vh、vw
  17. css实现跳动的心形图案
  18. win11蓝牙无法使用 Windows11蓝牙无法使用的解决方法
  19. spring及boot注解
  20. 【代码笔记】持续更新:知识图谱——gensim.corpora

热门文章

  1. Nacos配置中心持久化到MySQL数据库(创建MySQL数据库、Nacos配置MySQL数据库连接、Nacos添加配置 )
  2. Android_73b4电视如何更新,如何对电视机系统固件(软件)进行更新?
  3. php微信公众号首次关注自动回复,PHP_PHP微信开发之文本自动回复,首先,先去微信公众平台注册 - phpStudy...
  4. Vue.js简介和入门使用
  5. 人机智能融合之哲学探析
  6. 为什么我的Windows 10 便签不支持更改字体?
  7. 把时间当作朋友——现实
  8. 超级炫酷的游戏浏览器-Opera GX-附文件下载
  9. 富华力鼎:短视频拍摄技巧有哪些
  10. DIY狂人自制3D打印机 可打印食物