在前面的文章中,讲解了注解和编译时注解等一些列相关的内容,为了更加全面和真是的了解
编译时注解在实战项目中的使用,本文采取实现主流框架
- Android
注入
- butterknife
去全面认识编译时注解。
- view
先来张图压压惊,实现效果
的
- butterknife
绑定
- view
仿照
实现了
- butterknife
注解,通过
- @BindView
方法绑定当前
- WzgJector.bind
, 整体和
- MainActivity
使用完全一模一样,这里为了区分简单的把
- butterknife
改名了
- butterknife
- WzgJector
- public class MainActivity extends AppCompatActivity {
- @BindView(R.id.tv_msg)
- TextView tvMsg;
- @BindView(R.id.tv_other)
- TextView tvOther;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- WzgJector.bind(this);
- if(tvMsg!=null){
- tvMsg.setText("我已经成功初始化了");
- }
- if(tvOther!=null){
- tvOther.setText("我就来看看而已");
- }
- }
- }
实现的思路和实现原理大致一样,所以这里不重复阐述重复的步骤,重点讲解提升的技术点,所以需要在了解基本编译时注解的前提下继续下面的学习
这里使用了
和
- java
自带的注解,初始一个
- android
注解,同时指定了
- BindView
为
- @Target
, 注解
- FIELD
带有一个初始的
- BindView
参数及时使用时的
- int
- view-id
- @Retention(RetentionPolicy.CLASS)
- @Target(ElementType.FIELD)
- public @interface BindView {
- int value();
- }
对
和
- java
自带的注解不太清楚的同学可参考下面两篇文章
- android
详解
- Element
- Element
有了注解,必然需要有一个对应的注解处理器去处理注解,但是在处理注解的时候需要充分的了解注解处理器中的
方法及时核心的编译代码,而
- process
方法的核心便是
- process
对象,所以在讲解注解处理器前,需要对
- Element
有全面的认识,方能事半功倍。
- Element
由于
的知识内容的复杂性,这里重点讲解核心内容,基本使用完全是足够了
- Element
源码:
- public interface Element extends AnnotatedConstruct {
- TypeMirror asType();
- ElementKind getKind();
- Set getModifiers();
- Name getSimpleName();
- Element getEnclosingElement();
- List getEnclosedElements();
- boolean equals(Object var1);
- int hashCode();
- List getAnnotationMirrors();
可看出其实
是定义的一个接口,定义了外部调用暴露出的接口
- Element
方法 | 解释 |
---|---|
asType | 返回此元素定义的类型 |
getKind | 返回此元素的种类:包、类、接口、方法、字段…, 如下枚举值 |
getModifiers | 返回此元素的修饰符, 如下枚举值 |
getSimpleName | 返回此元素的简单名称, 比如 activity 名 |
getEnclosingElement | 返回封装此元素的最里层元素,如果此元素的声明在词法上直接封装在另一个元素的声明中,则返回那个封装元素; 如果此元素是顶层类型,则返回它的包如果此元素是一个包,则返回 null; 如果此元素是一个泛型参数,则返回 null. |
getAnnotation | 返回此元素针对指定类型的注解(如果存在这样的注解),否则返回 null。注解可以是继承的,也可以是直接存在于此元素上的 |
方法
- getKind
其中
方法比较特殊,
- getKind
方法来获取具体的类型,方法返回一个枚举值
- getKind()
- TypeKind
源码:
- public enum TypeKind {
- /** The primitive type boolean}. */
- BOOLEAN,
- /** The primitive type byte}. */
- BYTE,
- /** The primitive type short}. */
- SHORT,
- /** The primitive type int}. */
- INT,
- /** The primitive type long}. */
- LONG,
- /** The primitive type char}. */
- CHAR,
- /** The primitive type float}. */
- FLOAT,
- /** The primitive type double}. */
- DOUBLE,
- /** The pseudo-type corresponding to the keyword void}. */
- VOID,
- /** A pseudo-type used where no actual type is appropriate. */
- NONE,
- /** The null type. */
- NULL,
- /** An array type. */
- ARRAY,
- /** A class or interface type. */
- DECLARED,
- /** A class or interface type that could not be resolved. */
- ERROR,
- /** A type variable. */
- TYPEVAR,
- /** A wildcard type argument. */
- WILDCARD,
- /** A pseudo-type corresponding to a package element. */
- PACKAGE,
- /** A method, constructor, or initializer. */
- EXECUTABLE,
- /** An implementation-reserved type. This is not the type you are looking for. */
- OTHER,
- /** A union type. */
- UNION,
- /** An intersection type. */
- INTERSECTION;
- }
子类
- Element
有五个直接子接口,它们分别代表一种特定类型的元素
- Element
Tables | Are |
---|---|
TypeElement | 一个类或接口程序元素 |
VariableElement | 一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数 |
ExecutableElement | 某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素 |
PackageElement | 一个包程序元素 |
TypeParameterElement | 一般类、接口、方法或构造方法元素的泛型参数 |
五个子类各有各的用处并且有各种独立的方法,在使用的时候可以强制将
对象转换成其中的任一一种,但是前提是满足条件的转换,不然会抛出异常。
- Element
其中最核心的两个子分别是
和
- TypeElement
- VariableElement
详解
- TypeElement
定义的一个类或接口程序元素,相当于当前注解所在的
- TypeElement
对象,及时本案例使用代码中的
- class
- MainActivity
源码如下:
- public interface TypeElement extends Element,
- Parameterizable,
- QualifiedNameable {
- List getEnclosedElements();
- NestingKind getNestingKind();
- Name getQualifiedName();
- Name getSimpleName();
- TypeMirror getSuperclass();
- List getInterfaces();
- List getTypeParameters();
- Element getEnclosingElement();
- }
这里讲解主要的方法的含义
方法 | 解释 |
---|---|
getNestingKind | 返回此类型元素的嵌套种类 |
getQualifiedName | 返回此类型元素的完全限定名称。更准确地说,返回规范 名称。对于没有规范名称的局部类和匿名类,返回一个空名称. |
getSuperclass | 返回此类型元素的直接超类。如果此类型元素表示一个接口或者类 java.lang.Object,则返回一个种类为 NONE 的 NoType |
getInterfaces | 返回直接由此类实现或直接由此接口扩展的接口类型 |
getTypeParameters | 按照声明顺序返回此类型元素的形式类型参数 |
详解
- VariableElement
源码:
- public interface VariableElement extends Element {
- Object getConstantValue();
- Name getSimpleName();
- Element getEnclosingElement();
- }
这里
除了拥有
- VariableElement
的方法以外还有以下两个方法
- Element
方法 | 解释 |
---|---|
getConstantValue | 变量初始化的值 |
getEnclosingElement | 获取相关类信息 |
注解处理器需要两个步骤的处理:
对
有了全面的了解过后,注解处理器便可很轻松的学习了,先来看看简单版本的
- Element
处理
- BindView
- Set elements = roundEnv.getElementsAnnotatedWith(BindView.class);
- //一、收集信息
- for (Element element : elements) {
- /*检查类型*/
- if (!(element instanceof VariableElement)) {
- return false;
- }
- VariableElement variableElement = (VariableElement) element;
- /*获取类信息*/
- TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
- /*类的绝对路径*/
- String qualifiedName = typeElement.getQualifiedName().toString();
- /*类名*/
- String clsName = typeElement.getSimpleName().toString();
- /*获取包名*/
- String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString();
- BindView annotation = variableElement.getAnnotation(BindView.class);
- int id = annotation.value();
- /*参数名*/
- String name = variableElement.getSimpleName().toString();
- /*参数对象类*/
- String type = variableElement.asType().toString();
- ClassName InterfaceName = ClassName.bestGuess("com.example.annotation.api.ViewInjector");
- ClassName host = ClassName.bestGuess(qualifiedName);
- MethodSpec main = MethodSpec.methodBuilder("inject")
- .addModifiers(Modifier.PUBLIC)
- .returns(void.class)
- .addAnnotation(Override.class)
- .addParameter(host, "host")
- .addParameter(Object.class, "object")
- .addCode(""
- + " if(object instanceof android.app.Activity){\n"
- + " host." + name + " = (" + type + ")(((android.app.Activity)object).findViewById(" + id + "));\n"
- + " }\n"
- + "else{\n"
- + " host." + name + " = (" + type + ")(((android.view.View)object).findViewById(" + id + "));\n"
- + "}\n")
- .build();
- TypeSpec helloWorld = TypeSpec.classBuilder(clsName + "ViewInjector")
- .addModifiers(Modifier.PUBLIC)
- .addMethod(main)
- .addSuperinterface(ParameterizedTypeName.get(InterfaceName, host))
- .build();
- try {
- // 生成 com.example.HelloWorld.java
- JavaFile javaFile = JavaFile.builder(packageName, helloWorld)
- .addFileComment(" This codes are generated automatically. Do not modify!")
- .build();
- //生成文件
- javaFile.writeTo(filer);
- } catch (IOException e) {
- e.printStackTrace();
- }
大体的思路,先判断
类型,如果是
- Element
则继续获取相关的包名(这里必须在
- VariableElement
包名一致,不然获取不到
- app
类)类对象信息,以及
- android
注解修饰的参数数据; 最后将所有需要的数据通过
- @BindView
和
- javapoet
自动编译创建一个 java 文件
- Filer
最后得到的生成类:
- package com.wzgiceman.viewinjector;
- import com.example.ViewInjector;
- import java.lang.Object;
- import java.lang.Override;
- public class MainActivityViewInjector implements ViewInjector<MainActivity> {
- @Override
- public void inject(MainActivity host, Object object) {
- if(object instanceof android.app.Activity){
- host.tvMsg = (android.widget.TextView)(((android.app.Activity)object).findViewById(2131492945));
- }
- else{
- host.tvMsg = (android.widget.TextView)(((android.view.View)object).findViewById(2131492945));
- }
- }
- }
上面的简单处理器中,只是单纯的判断一个注解情况,在信息收集的处理上简化了,导致当前处理器只能同时处理当前相同类中的莫一个注解这里只初始化了
对象,
- tvMsg
并没有初始化,当然这是不符合实际需求的,下面来优化收集和处理方案。
- tvOther
优化方案其实就是多了一步信息的记录的工作
首先创建一个类信息对象,其中包含了一下的属性,其中
便是记录当前类中所有注解相关的信息
- varMap
- public class VariMsg {
- /*包名*/
- private String pk;
- /*类名*/
- private String clsName;
- /*注解对象*/
- private HashMap varMap;
- }
- BindViewProcessors
1. 初始一个 map 记录
对象, 因为 process 方法可能会多次调用,所以需要每次都
- VariMsg
一遍
- clear
- Map veMap = new HashMap<>();
2. 记录信息
通过
记录所有的相关信息,并且每次需要判断是否重复,剔除重复的数据。
- veMap
- for (Element element : elements) {
- /*检查类型*/
- if (!(element instanceof VariableElement)) {
- return false;
- }
- VariableElement variableElement = (VariableElement) element;
- /*获取类信息*/
- TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
- /*类的绝对路径*/
- String qualifiedName = typeElement.getQualifiedName().toString();
- /*类名*/
- String clsName = typeElement.getSimpleName().toString();
- /*获取包名*/
- String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString();
- BindView annotation = variableElement.getAnnotation(BindView.class);
- int id = annotation.value();
- VariMsg variMsg = veMap.get(qualifiedName);
- if (variMsg == null) {
- variMsg = new VariMsg(packageName, clsName);
- variMsg.getVarMap().put(id, variableElement);
- veMap.put(qualifiedName, variMsg);
- } else {
- variMsg.getVarMap().put(id, variableElement);
- }
- }
3. 通过
去生成
- javapoet
类文件 这里主要是
- java
的运用,详细用法可去
- javapoet
查看
- javapoet
- System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
- for (String key : veMap.keySet()) {
- ClassName InterfaceName = ClassName.bestGuess("com.example.ViewInjector");
- ClassName host = ClassName.bestGuess(key);
- VariMsg variMsg = veMap.get(key);
- StringBuilder builder = new StringBuilder();
- builder.append(" if(object instanceof android.app.Activity){\n");
- builder.append(code(variMsg.getVarMap(), "android.app.Activity"));
- builder.append("}\n");
- builder.append("else{\n");
- builder.append(code(variMsg.getVarMap(), "android.view.View"));
- builder.append("}\n");
- MethodSpec main = MethodSpec.methodBuilder("inject")
- .addModifiers(Modifier.PUBLIC)
- .returns(void.class)
- .addAnnotation(Override.class)
- .addParameter(host, "host")
- .addParameter(Object.class, "object")
- .addCode(builder.toString())
- .build();
- TypeSpec helloWorld = TypeSpec.classBuilder(variMsg.getClsName() + "ViewInjector")
- .addModifiers(Modifier.PUBLIC)
- .addMethod(main)
- .addSuperinterface(ParameterizedTypeName.get(InterfaceName, host))
- .build();
- try {
- JavaFile javaFile = JavaFile.builder(variMsg.getPk(), helloWorld)
- .addFileComment(" This codes are generated automatically. Do not modify!")
- .build();
- javaFile.writeTo(filer);
- } catch (IOException e) {
- e.printStackTrace();
- System.out.println("e--->" + e.getMessage());
- }
- }
- System.out.println("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
- return true;
- }
- /**
- * 根据注解对象生成code方法体
- *
- * @param map
- * @param pk
- * @return
- */
- private String code(Map map, String pk) {
- StringBuilder builder = new StringBuilder();
- for (Integer id : map.keySet()) {
- VariableElement variableElement = map.get(id);
- String name = variableElement.getSimpleName().toString();
- String type = variableElement.asType().toString();
- builder.append("host." + name + " = (" + type + ")(((" + pk + ")object).findViewById(" + id + "));\n");
- }
- return builder.toString();
- }
到这里注解处理器最终版本就生成成功了,看下最后生成的代码类
- package com.wzgiceman.viewinjector;
- import com.example.ViewInjector;
- import java.lang.Object;
- import java.lang.Override;
- public class MainActivityViewInjector implements ViewInjector<MainActivity> {
- @Override
- public void inject(MainActivity host, Object object) {
- if(object instanceof android.app.Activity){
- host.tvMsg = (android.widget.TextView)(((android.app.Activity)object).findViewById(2131492945));
- host.tvOther = (android.widget.TextView)(((android.app.Activity)object).findViewById(2131492946));
- }
- else{
- host.tvMsg = (android.widget.TextView)(((android.view.View)object).findViewById(2131492945));
- host.tvOther = (android.widget.TextView)(((android.view.View)object).findViewById(2131492946));
- }
- }
- }
- api
模块主要定义的是给外部提供的使用方法,这里使用方法便是
- api
方法,相同于
- WzgJector.bind(this)
中的
- Butter Knife
- ButterKnife.bind(this);
- public class MainActivity extends AppCompatActivity {
- xxxxxx
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- WzgJector.bind(this);
- xxxx
- }
- }
创建一个
- app module
- ViewInjector
暴露的外部方法,主要在编译自动生成的注解处理类中使用
- /**
- * 接口
- * Created by WZG on 2017/1/11.
- */
- public interface ViewInjector<M> {
- void inject(M m, Object object);
- }
- WzgJector
提供了两个方法,一种是
绑定,一种是
- activity
或者
- view
绑定,绑定完以后,通过反射得到相关注解编译处理类及时
- fragment
子类对象,调用
- ViewInjector
方法完成初始过程。
- inject(M m, Object object)
- public class WzgJector {
- public static void bind(Object activity) {
- bind(activity, activity);
- }
- public static void bind(Object host, Object root) {
- Class clazz = host.getClass();
- String proxyClassFullName = clazz.getName() + "ViewInjector";
- try {
- Class proxyClazz = Class.forName(proxyClassFullName);
- ViewInjector viewInjector = (ViewInjector) proxyClazz.newInstance();
- viewInjector.inject(host, root);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (InstantiationException e) {
- e.printStackTrace();
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
- }
到这里其实会发现,编译时注解并不是完全不使用反射,但是它避免了关键性重复性代码的多次反射使用,继而提升了编译的性能。
到这里
的
- butterknife
实现原理基本就是如此,由于时间原因
- @BindView
就不再讲解了。其实原理一样,小伙伴们可以自己安装本文思路添加
- @OnClick
的处理。
- @OnClick
来源: