在前面的文章中,讲解了注解和编译时注解等一些列相关的内容,为了更加全面和真是的了解Android
编译时注解在实战项目中的使用,本文采起实现主流框架butterknife
注入view
去全面认识编译时注解。javascript
注解专栏-博客html
先来张图压压惊,实现效果butterknife
的view
绑定java
仿照butterknife
实现了@BindView
注解,经过WzgJector.bind
方法绑定当前MainActivity
,总体和butterknife
使用彻底如出一辙,这里为了区分简单的把butterknife
更名了WzgJector
android
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("我就来看看而已");
}
}
}复制代码
实现的思路和Android
编译时注解-初认识实现原理大体同样,因此这里不重复阐述重复的步骤,重点讲解提高的技术点,因此须要在了解基本编译时注解的前提下继续下面的学习git
Android
编译时注解-初认识github
这里使用了java
和android
自带的注解,初始一个BindView
注解,同时指定了@Target
为FIELD
,注解BindView
带有一个初始的int
参数及时使用时的view-id
api
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}复制代码
对java
和android
自带的注解不太清楚的同窗可参考下面两篇文章app
Android-注解详解ide
Element
详解Element
有了注解,必然须要有一个对应的注解处理器去处理注解,可是在处理注解的时候须要充分的了解注解处理器中的process
方法及时核心的编译代码,而process
方法的核心即是Element
对象,因此在讲解注解处理器前,须要对Element
有全面的认识,方能事半功倍。
因为Element
的知识内容的复杂性,这里重点讲解核心内容,基本使用彻底是足够了
源码:
public interface Element extends AnnotatedConstruct {
TypeMirror asType();
ElementKind getKind();
Set<Modifier> getModifiers();
Name getSimpleName();
Element getEnclosingElement();
List<? extends Element> getEnclosedElements();
boolean equals(Object var1);
int hashCode();
List<? extends AnnotationMirror> getAnnotationMirrors();
<A extends Annotation> A getAnnotation(Class<A> var1);
<R, P> R accept(ElementVisitor<R, P> var1, P var2);
}复制代码
可看出其实Element
是定义的一个接口,定义了外部调用暴露出的接口
方法 | 解释 |
---|---|
asType | 返回此元素定义的类型 |
getKind | 返回此元素的种类:包、类、接口、方法、字段...,以下枚举值 |
getModifiers | 返回此元素的修饰符,以下枚举值 |
getSimpleName | 返回此元素的简单名称,好比activity名 |
getEnclosingElement | 返回封装此元素的最里层元素,若是此元素的声明在词法上直接封装在另外一个元素的声明中,则返回那个封装元素; 若是此元素是顶层类型,则返回它的包若是此元素是一个包,则返回 null; 若是此元素是一个泛型参数,则返回 null. |
getAnnotation | 返回此元素针对指定类型的注解(若是存在这样的注解),不然返回 null。注解能够是继承的,也能够是直接存在于此元素上的 |
getKind
方法其中getKind
方法比较特殊,getKind()
方法来获取具体的类型,方法返回一个枚举值TypeKind
源码:
public enum TypeKind {
/** The primitive type {@code boolean}. */
BOOLEAN,
/** The primitive type {@code byte}. */
BYTE,
/** The primitive type {@code short}. */
SHORT,
/** The primitive type {@code int}. */
INT,
/** The primitive type {@code long}. */
LONG,
/** The primitive type {@code char}. */
CHAR,
/** The primitive type {@code float}. */
FLOAT,
/** The primitive type {@code double}. */
DOUBLE,
/** The pseudo-type corresponding to the keyword {@code 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<? extends Element> getEnclosedElements();
NestingKind getNestingKind();
Name getQualifiedName();
Name getSimpleName();
TypeMirror getSuperclass();
List<? extends TypeMirror> getInterfaces();
List<? extends TypeParameterElement> 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 | 获取相关类信息 |
注解处理器须要两个步骤的处理:
1.收集先关的信息
2.生成处理类
对Element
有了全面的了解事后,注解处理器即可很轻松的学习了,先来看看简单版本的BindView
处理
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
//1、收集信息
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
和Filer
自动编译建立一个java文件
最后获得的生成类:
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<Integer,VariableElement> varMap;
}复制代码
BindViewProcessors
1.初始一个map记录VariMsg
对象,由于process方法可能会屡次调用,因此须要每次都clear
一遍
Map<String, VariMsg> 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<Integer, VariableElement> 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
的处理。