| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 移动开发 -> ButterKnife源码探究(附实现自定义注解处理器) -> 正文阅读 |
|
[移动开发]ButterKnife源码探究(附实现自定义注解处理器) |
这样就只需要在MainActivity的onCreate方法中调用MainActivity_Inject的构造方法就能完成成员变量的初始化了。 所以接下来的任务就是解析在代码中使用的注解,根据注解生成MainActivity_Inject.java这个文件 (3)解析注解 新建一个Java Library模块命名为compiler,在其中创建一个类InjectProcessor,继承自 //配置注解处理器支持处理的注解类型为InjectString和InjectInt @SupportedAnnotationTypes({“com.example.annotations.InjectString”, “com.example.annotations.InjectInt”}) //配置注解处理器支持的Java版本 @SupportedSourceVersion(SourceVersion.RELEASE_7) @AutoService(Processor.class) //定义注解处理器继承自AbstractProcessor public class InjectProcessor extends AbstractProcessor{ private static final ClassName CONTEXT = ClassName.get(“android.content”, “Context”); //待生成java文件的的集合,key为被注解的类的类名,value为GenerateJavaFile对象 private HashMap<String,GenerateJavaFile> mGenerateJavaFiles = new HashMap<String, GenerateJavaFile>(); /**
*/ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //遍历所有的TypeElement的,一个注解类型对应一个TypeElement for (TypeElement typeElement : set) { //遍历在代码中使用typeElement对应注解类型来注解的元素 //例如:如果typeElement对应的是InjectString注解类型,那么Element对应为使用@InjectString注解的成员变量 for (Element element : roundEnvironment.getElementsAnnotatedWith(typeElement)) { //添加注解元素到将要生成的java文件对应的GenerateJavaFile的对象中 addElementToGenerateJavaFile(element); } } //生成java文件 createJavaFile(); return true; } } 因为之前定义的InjectString和InjectInt是类,所以他们的Element类型是TypeElement,一个TypeElement对应一个注解类型,所以process传入的set集合中有两个TypeElement对象,分别对应InjectString和InjectInt,我们遍历set集合,处理每个注解类型。通过调用roundEnvironment的getElementsAnnotatedWith(typeElement)来获取一个注解类型注解了哪些元素。 比如我们使用了InjectStirng注解了两个字符串成员变量: @InjectString public String hello; @InjectString public String world; 在InjectStirng的TypeElement会遍历hello和world这两个Element 。因为我们要将注解的元素转化成Java代码,所以需要处理每个Element对象,为生成Java文件做准备。我们定义类GenerateJavaFile来描述一个待生成的Java文件在InjectProcessor中: /**
*/ private static class GenerateJavaFile { String packageName;//包名 String className;//类名 List elements;//注解元素集合 } 通过调用addElementToGenerateJavaFile方法创建GenerateJavaFile对象,并将Element对象加入到GenerateJavaFile对象内部的elements集合中。addElementToGenerateJavaFile实现如下: /**
*/ private void addElementToGenerateJavaFile(Element element) { //获取element对应成员变量所在的类,即被注解的类 TypeElement typeElement = (TypeElement) element.getEnclosingElement(); String[] split = typeElement.getQualifiedName().toString().split("\."); String className = split[split.length - 1]; //通过父类的processingEnv获取报信者,用于在编译过程中打印log Messager messager = processingEnv.getMessager(); messager.printMessage(Diagnostic.Kind.NOTE, "add element to generate file " + className); //获取被注解类对应的GenerateJavaFile对象,如果没有,则创建 GenerateJavaFile generateJavaFile = mGenerateJavaFiles.get(className); if (generateJavaFile == null) { GenerateJavaFile file = new GenerateJavaFile(); //设置待生成java文件的包名 file.packageName = processingEnv.getElementUtils().getPackageOf(element).toString(); //设置待生成java文件的类名 file.className = className + “_Inject”; //初始化元素集合 file.elements = new ArrayList(); file.elements.add(element); //保存被注解类所对应要生成java类的GenerateJavaFile对象 mGenerateJavaFiles.put(className, file); } else { //将注解元素添加到有的generateJavaFile对象中 generateJavaFile.elements.add(element); } } (4)生成Java源文件 解析完所有的注解,创建GenerateJavaFile对象后,就可以根据GenerateJavaFile对象来生成对应的Java文件,这里可以使用第三方库javapoet来完成Java文件的生成,它的使用方式可以参考blog,这里不再赘述。 在compiler模块的build.gradle下添加javapoet的依赖同步: compile ‘com.squareup:javapoet:1.9.0’ 接着实现createJavaFile文件方法,来完成Java文件的生成,遍历GenerateJavaFile集合,一个GenerateJavaFile对象对应一个要生成的Java文件,首先创建这个Java类的构造方法: //createJavaFile() //构建一个构造方法,该构造方法带有一个Context类型的参数 MethodSpec.Builder builder = MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(CONTEXT, “context”); 参数类型CONTEXT定义为: private static final ClassName CONTEXT = ClassName.get(“android.content”, “Context”); 然后遍历注解元素,一个Element对象对应一条赋值代码: //遍历该类中需要处理的注解元素 for (Element element: file.elements) { //如果注解的成员变量是一个int类型 if (element.asType().toString().equals(“int”)) { //在构造方法中添加一条语句 //例如:((MainActivity)context).one = context.getResources().getInteger(R.integer.one); builder.addStatement("(( N ) c o n t e x t ) . N)context). N)context).N = context.getResources().getInteger(R.integer.$N)", file.className.split("_")[0], element.getSimpleName(), element.getSimpleName()); //如果注解的是一个String类型 } else if (element.asType().toString().equals(“java.lang.String”)) { //在构造方法中添加一条语句 //例如:((MainActivity)context).hello = context.getResources().getString(R.string.hello); builder.addStatement("(( N ) c o n t e x t ) . N)context). N)context).N = context.getResources().getString(R.string.$N)", file.className.split("_")[0], element.getSimpleName(), element.getSimpleName()); } } 最后构建一个类,输出Java文件: //构建一个类,添加一个上述的构造方法 TypeSpec typeSpec = TypeSpec.classBuilder(file.className) .addModifiers(Modifier.PUBLIC) .addMethod(builder.build()) .build(); try { //输出java文件 JavaFile javaFile = JavaFile.builder(file.packageName, typeSpec).build(); javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } (5)注解处理器,开始构建 注解处理器创建完后,还需要注册,这里最简单的方法是使用AutoService工具注册,在compiler的build.gradle添加依赖: compile ‘com.google.auto.service:auto-service:1.0-rc3’ 然后给InjectProcess添加AutoService注解: @AutoService(Processor.class) //定义注解处理器继承自AbstractProcessor public class InjectProcessor extends AbstractProcessor{ 。。。。 } AutoService的作用是生成一个配置文件来注册注解处理器。该配置文件的内容为com.example.InjectProcessor。 一切准备就绪,我们在app模块下添加依赖: dependencies{ annotationProcessor project(’:compiler’) } 在项目目录terminal下输入:gradle clean和gradle build,来构建一遍项目,构建成功后会在app模块下的路径:build/generated/source/apt/debug/包名 下生成一个MainActivity_Inject.java文件。结果与期望的代码一致: public class MainAcivity_Inject{ public MainActivity_Inject(Context context){ ((MainActivity)context).one=context.getResources().getInteger(R.integer.one); ((MainActivity)context).two=context.getResources().getInteger(R.integer.two); ((MainActivity)context).hello=context.getResources().getInteger(R.integer.hello); ((MainActivity)context).world=context.getResources().getInteger(R.integer.world); } } 到这里就完成一个自定义的注解器啦,最后我们将one、two输出在屏幕上得出的就是1、2。 从结果来看注解器帮我们做的事情就是 另one=1、tow=2、hello=‘hello’,world=‘world’这样子。 我们再来对自定义注解器的过程做一个小结:
创建一个Java Library,在这里通过@Interface来定义注解类,并声明它要修饰的变量类型(类、成员变量等等)。
在build.gradle中添加依赖的模块,并在Activity中使用注解绑定一个控件或者变量。
创建一个Java Library作为注解器,表明支持的注解类型,并且注解类要继承AbstractProcessor, 实现其process方法,在process方法中会传入一个set和roundEnviroment回合环境,set里面是注解类型的集合,通过遍历set,我们所有被注解的对象加入到自己准备的文件中。这个文件存储所有被注解的Element。
在创建完文件后,我们需要在程序编译时让这个文件生成一个正式的文件类,就是类似于MainActivity_ViewBinding那样的文件类,我们通过第三方库javapoet来完成Java文件的生成。通过MethodSpec.Build为这个类设置构造方法,并格式化代码(如为每个Element使用FindViewById)。最后输出这个Java文件。
通过第三方库AutoService来为当前项目注册这个注解处理器。注册完后就可以使用编译时注解了。 下面来分析ButterKnife的源码,ButterKnife的Java Library为butter-annotation模块,下面有其所有注解,包括常用的bindView和onClick。 1、注解定义: @Retention(RUNTIME) @Target(FIELD) public @interface BindView { /** View ID to which the field will be bound. */ @IdRes int value(); } 可以看到BindeView注解保留到class文件中,并且是修饰成员变量的。另外内部有一个int类型的value方法,这是注解参数的定义,表示在使用BindView注解时可以接收一个int类型的参数,并且使用了注解@IdRes,表示参数必须是一个资源id。 这就体现出了我们使用BindView的格式:@BindeView(R.id.xxxx) 2、解析注解 在模块butterknife-compiler中,定义了注解处理器ButterKnifeProcessor类。 ButterKnifeProcessor同样使用process方法来完成注解的解析,里面的逻辑脉络十分清晰。 首先通过findAndParesTargets方法来获取所有注解来解析成一个Map集合,Map集合的key为一个TypeElement类型(比如我们在MainActivity中定义了注解,那么比会有一个TypeElement的对象对应MainActivity),表示被注解的类,value为一个BindingSet对象,里面有待生成的Java类所需的所有信息,包括类名包名、绑定的控件(比如我们生成MainActivity对应的绑定类MainActivity_ViewBinding所需要的所有信息)。 然后遍历Map集合中的所有元素,使用BindingSet对象创建一个JavaFile对象,进而生成一个Java文件。 我们这里主要分析 上半部分是解析被BindView注解的元素,下半部分是将整合好的信息传入到最后的bindMap并返回这个Map。 在findAndParseTargets 方法中会遍历所有被注解的元素,我们只看一下最常用的注解@BindView是如何被解析的,也就是 在parseBindView中会对被注解的元素做可访问检查和包名检查,被绑定的元素必须继承View类或者一个接口,然后从builderMap集合中获取被注解的元素所在的类对应的BindingSet.Builder对象,如果没有,则调用 我们可以在BindingSet的newBuilder方法中得到一些有用的信息,比如将要生成的Java文件的包名、类名等: 3、生成Java文件 BindingSet对象准备好之后,就可以使用BindingSet对象来生成一个对应的Java文件,process方法中调用BindingSet对象的brewJava 方法来“酿制”一个Java文件。brewJava就和我们自定义实现一个注解处理器中用到的Javapoet一样,调用 这个方法中首先调用 |
|
移动开发 最新文章 |
Vue3装载axios和element-ui |
android adb cmd |
【xcode】Xcode常用快捷键与技巧 |
Android开发中的线程池使用 |
Java 和 Android 的 Base64 |
Android 测试文字编码格式 |
微信小程序支付 |
安卓权限记录 |
知乎之自动养号 |
【Android Jetpack】DataStore |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 10:25:52- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |