Java注解在实际应用中很广泛,目前很多主流的框架也采用了注解来提高效率,其实注解就是Java代码中的一个标记,也可以将它理解为对象,它有自己的相关属性和值,只是不实现相关方法而已。下面我们通过一个例子来分析一下注解。
public class Test {
//添加自定义注解
@FunAnno(name="我是方法a")
public void fun_a(){
LogUtils.d("执行方法a");
}
//添加java内置的注解
@Deprecated
@SuppressWarnings("uncheck")
public void fun_b(){
}
/**
* 定义一个注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FunAnno{
String name() default "";
}
}
上面代码中我们自定义了一个注解@FunAnno,注解用@interface来声明,@Target(ElementType.METHOD)代表此注解应用在方法上,@Retention(RetentionPolicy.RUNTIME)代表此注解的生命周期保存到运行时,String name() default ""用来声明一个String类型的注解元素,我们可以在使用注解的时候对其进行赋值,当然在声明的时候要用default定义其初始值。然后我们在fun_a方法中使用了@FunAnno注解。@Deprecated和@SuppressWarnings(“uncheck”)都是Java内置的注解,下面我们将会介绍它们的意义。
一:注解的语法
在上面例子中,我们定义注解的时候用到了@Target和@Retention注解,其实这两个是Java提供的元注解,所谓的元注解就是标记其他注解的注解,我们常见的元注解有:@Target、@Retention、@Documented、@Inherited、@Repeatable(java8新增)。
1. @Target
用来约束注解应用的地方(比如方法、类、字段等等),其注解元素接收一个枚举数组ElementType[],代表注解应用范围
//Target注解源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
//value范围
public enum ElementType {
/**标明该注解可以用于类、接口(包括注解类型)或enum声明*/
TYPE,
/** 标明该注解可以用于字段(域)声明,包括enum实例 */
FIELD,
/** 标明该注解可以用于方法声明 */
METHOD,
/** 标明该注解可以用于参数声明 */
PARAMETER,
/** 标明注解可以用于构造函数声明 */
CONSTRUCTOR,
/** 标明注解可以用于局部变量声明 */
LOCAL_VARIABLE,
/** 标明注解可以用于注解声明(应用于另一个注解上)*/
ANNOTATION_TYPE,
/** 标明注解可以用于包声明 */
PACKAGE,
/**
* 标明注解可以用于类型参数声明(1.8新加入)
*/
TYPE_PARAMETER,
/**
* 类型使用声明(1.8新加入)
*/
TYPE_USE
}
当Target未表明任何value时,代表此注解可以应用到任何元素上,还可以采用数组的方式表明value值,例如:@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})表明注解可以应用的范围是{}内的所有类型。
2.@Retention
该注解用来约束注解的生命周期,其接收三个值,分别是SOURCE(源码级别)、CLASS(类文件级别)、RUNTIME(运行时级别),详解如下:
-
SOURCE:该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里。
-
CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解@SuppressWarnning等。
-
RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息)。
3.@Documented
@Documented 可以让被修饰的注解上传javadoc
4.@Inherited
可以让注解被继承,这里继承的意思是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解,如下:
public class InheritedTest {
/**
* 被Inherited修饰的注解
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoA {
}
/**
* 没有Inherited修饰的注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoB {
}
@AnnoA
class A{
}
class B extends A{
}
@AnnoB
class C{
}
class D extends C{
}
public void test(){
// A instanceA=new A();
// D instanceD=new D();
LogUtils.d("使用了Inherited注解的情况:"+ Arrays.toString(B.class.getAnnotations()));
LogUtils.d("没有使用了Inherited注解的情况:"+ Arrays.toString(D.class.getAnnotations()));
}
}
运行结果如下:
上面我们讲解了注解的基本语法结构和常用的元注解分析,下面我们看看注解的元素值及其数据结构类型
二:注解元素及其数据类型
上面我们看到在用@FunAnno注解的时候,传进了一个值name=“我是方法a”,这个name就是注解元素,其数据类型为String。我们在定义注解的时候,常常都会包含一些元素及其值,方便注解处理器使用。如下:
/**
* 使用注解标记Person类,给注解元素赋值
*/
@Person.PersonInfo(name = "王大锤",age = 18,male = true)
public class Person {
/**
* 定义一个注解,包含有三个注解元素,分别代表名字、年龄、性别
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PersonInfo{
String name() default "";
int age() default -1;
boolean male() default false;
}
/**
* 注解元素的简化使用方式,只定义value()即可
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Age{
int value() default -1;
}
/**
* 注解的简化使用
*/
@Age(20)
class Lily{
}
}
上面代码我们展示了注解元素的定义及其使用,注意简化方式的使用,如果简化方式中不止一个元素的话, 还是要使用value=“”的方式去赋值元素的。下面是注解元素支持的数据类型:
-
所有基本类型(int,float,boolean,byte,double,char,long,short)
-
String
-
Class
-
enum
-
Annotation 注解嵌套
-
上述类型的数组
下面我们用例子来展示所有注解元素数据类型的使用:
public class AnnoElement {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoRef{
int value() default -1;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoElements{
//定义性别枚举
enum Gender{MALE,FEMALE};
//声明枚举
Gender gender() default Gender.FEMALE;
//声明string
String name() default "";
//声明int
int age() default -1;
//声明class类型
Class<?> Person() default Void.class;
//注解嵌套
AnnoRef ref() default @AnnoRef;
//数组类型
String[] strs() default {""};
}
/**
* 注解元素的使用
*/
@AnnoElements(gender = AnnoElements.Gender.MALE,name = "java",
age = 100,Person = Person.class,ref = @AnnoRef(10),strs = {"a","b"})
class Test{
}
}
注解元素对默认值有严格的要求,对象不能用null表示,所以一般我们都用一些没有意义的值来表示默认值。像这些注解元素及其对应的值,我们的注解处理器是可以获取的,从而可以作为我们后面相关处理的依据。
三:Java内置注解
Java内置注解主要有三个,我们分别来看一下:
- @Override:用于标明此方法覆盖了父类的方法,源码如下
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
- Deprecated:用于标明已经过时的方法或类,源码如下
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
- @SuppressWarnnings:用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告,其实现源码如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
其中value值接收如下:
deprecation:使用了不赞成使用的类或方法时的警告;
unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
path:在类路径、源文件路径等中有不存在的路径时的警告;
serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
finally:任何 finally 子句不能正常完成时的警告;
all:关于以上所有情况的警告。
四:注解与反射的关系
在反射包的相关类中实现了都实现了AnnotatedElement接口,通过此接口我们可以利用反射技术获得对应类的相关注解信息,反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口,下面我们来看看AnnotatedElement中的相关方法:
方法名称 | 返回值 | 说明 |
getAnnotation(Class<A> annotationClass) | <A extends Annotation> | 如果此元素存在指定类型的注解,那么返回这些注解,否则返回null |
getAnnotations() | Annotation[] | 返回此元素的所有注解,包括从父类继承的 |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | boolean | 如果指定类型的注解存在次元素上,则返回true,否则返回false |
getDeclaredAnnotations() |
Annotation[] | 返回次元素上的所有注解,不包括从父类继承的注解 |
案例演示如下:
public class AnnoElementTest{
@AnnoElementTest.AnnoA
public static class A{
}
//B 继承A
@AnnoElementTest.AnnoB
public static class B extends A{
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoA {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoB {
}
public static void test(){
B instanceB=new B();
Class<?> classB=instanceB.getClass();
//根据指定注解类型获得注解
AnnoB annoB=classB.getAnnotation(AnnoB.class);
LogUtils.d("根据指定注解获取注解:"+annoB);
//获取此元素上的所有注解,包括从父类继承的
Annotation[] annotations=classB.getAnnotations();
LogUtils.d("获取所有注解包括继承的:"+ Arrays.toString(annotations));
//获取此元素上所有注解,不包括继承的
Annotation[] annotations1=classB.getDeclaredAnnotations();
LogUtils.d("获取所有注解不包括继承的:"+Arrays.toString(annotations1));
//判断注解AnnoA是否在次元素上
boolean is=classB.isAnnotationPresent(AnnoB.class);
LogUtils.d("是否="+is);
}
}
执行结果如下:
在这里要获取到父类继承的注解,此注解上必须有@Inherited标记,否则获取不到。
五:运行时注解和编译时注解
运行时注解,在运行时拿到类的Class对象,然后遍历其方法、变量,判断有无注解,然后做一些操作。
编译时注解,在java的编译阶段,根据注解标识,动态生成一些类或xml文件,在运行时期,这些注解是没有的,我们是依靠动态生成的一些类做操作,由于没有反射, 效率和直接调用方法没什么区别。
所以我们可以看到,编译时注解的效率要比运行时注解高,很多框架如ButterKnife、EventBus都是用的编译时注解。下面我们通过例子去看看,运行时注解和编译时注解是怎么的一个实现过程。
1.运行时注解的处理
我们以通过运行时注解去创建一个数据库表为例展开, 先来看看注解的定义,如下:
/**
* Created by XR_liu on 2018/11/22.
* 运行时注解
*/
public class RAnnotation {
/**
* 约束注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Contrains{
//是否为主键
boolean primaryKey() default false;
//是否允许为null
boolean allowNull() default false;
//是否唯一
boolean isUnique() default false;
}
/**
* 实体类注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity{
String tableName() default "";
}
/**
* 整型字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FeildInteger{
//对应的字段名
String name() default "";
//嵌套注解
Contrains contrain() default @Contrains;
}
/**
* 字符串字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FeildString{
String name() default "";
int varchar() default 30;
Contrains contrain() default @Contrains;
}
}
我们定义的上面几个注解,都是为创建一个数据库表服务的, 注意生命周期都应该标注为RUNTIME,只有这样我们才能在运行时通过反射获取相关信息。下面通过定义一个Person类来看注解的使用:
/**
* Created by XR_liu on 2018/11/22.
*/
@RAnnotation.Entity
public class Person {
//主键
@RAnnotation.FeildString(contrain = @RAnnotation.Contrains(primaryKey = true))
private String id;
@RAnnotation.FeildString
private String name;
@RAnnotation.FeildInteger
private int age;
//允许为null
@RAnnotation.FeildString(contrain = @RAnnotation.Contrains(allowNull = true))
private String address;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
下面我们就开始自己编写一个注解处理器,关键逻辑是通过Class对象去获取有注解的字段,然后再获取对应注解的相关信息,然后就可以根据这些信息去拼接一个SQL语句了,如下:
/**
* 通过一个实体类返回一个创建数据库表的SQL语句
*
* @return
*/
public static String createTableSql(Class<?> cl) {
String sql = null;
//获取class对象
Class<?> clazz = cl;
RAnnotation.Entity entity = clazz.getAnnotation(RAnnotation.Entity.class);
String tabelName; //数据库名称
if (entity == null || entity.tableName() == "") {
tabelName = clazz.getSimpleName().toUpperCase();
}
tabelName = entity.tableName();
//所有字段名的集合
List<String> columNames = new ArrayList<>();
//通过反射获取class的所有字段
for (Field field : clazz.getDeclaredFields()) {
String columName = null;
//获取字段上的所有注解
Annotation[] annotations = field.getDeclaredAnnotations();
if (annotations.length < 1) {
continue; //不属于表的字段
}
//整型字段
if (annotations[0] instanceof RAnnotation.FeildInteger) {
RAnnotation.FeildInteger feildInteger = (RAnnotation.FeildInteger) annotations[0];
//给字段名赋值
columName = "".equals(feildInteger.name()) ? field.getName().toUpperCase() : feildInteger.name();
//保存构建SQL语句片段
columNames.add(columName + " INT" + getContrains(feildInteger.contrain()));
}
//string字段
if (annotations[0] instanceof RAnnotation.FeildString) {
RAnnotation.FeildString feildString = (RAnnotation.FeildString) annotations[0];
columName = "".equals(feildString.name()) ? field.getName().toUpperCase() : feildString.name();
columNames.add(columName + " VARCHAR(" + feildString.varchar() + ")" + getContrains(feildString.contrain()));
}
//构建数据库表语句
StringBuilder createSql = new StringBuilder("CREATE TABLE " + tabelName + "(");
for (String colum : columNames) {
createSql.append("\n " + colum + ",");
}
sql = createSql.substring(0, createSql.length() - 1) + ")";
}
return sql;
我们来看一下执行结果:
到这里成功地返回了一个正确的SQL语句!附上源码:https://github.com/jiusetian/AndroidStudyData/tree/master/app/src/main/java/com/androidstudydata/annotation
上面是运行时注解的例子,其中关键的是利用Class对象去寻找注解的相关信息,下面我们来看看编译时注解又是怎么回事的。
2.编译时注解
编译时注解主要利用的注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解的工具,就是在源代码的编译阶段,通过注解处理器,我们可以获得文件中注解的相关内容。常见的用途是,我们在获得注解相关数据之后,通过去生成有规律的代码,解决编程过程中的重复工作,大大提高了效率,比如ButterKnife、Dagger2等框架。
下面我们模仿butterknife的实现原理,自己去实现一个类似功能的简单例子,首先我们在注解实现的Java Module的Gradle文件中添加如下配置:
compile 'com.google.auto.service:auto-service:1.0-rc2'//谷歌的帮助我们快速实现注解处理器
compile 'com.squareup:javapoet:1.7.0'//用来生成java文件的
定义一个注解@BindView,如下:
/**
* Created by XR_liu on 2018/11/24.
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default -1;
}
当然注解的生命周期是编译阶段,适用范围为字段。定义一个注入view实例公共接口,如下
/**
* Created by XR_liu on 2018/11/24.
*/
public interface ViewInject<T> {
//T是指使用注解的类,viewOwner是注解view的持有者
void inject(T t, Object viewOwner);
}
下面我们来看看怎么去实现这个注解处理器的,这个是关键,先看看代码:
/**
* Created by XR_liu on 2018/11/24.
*/
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor{
private Elements elementUtils;
private Map<String, CodeCreater> codeCreaterMap = new HashMap<String, CodeCreater>();
@Override
public SourceVersion getSupportedSourceVersion()
{
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv)
{
super.init(processingEnv);
//Elements mElementUtils;跟元素相关的辅助类,帮助我们去获取一些元素相关的信息。
elementUtils = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
codeCreaterMap.clear();
//element所代表的元素只在编译期可见,用于保存元素在编译期的各种状态,而Type所代表的元素是运行期可见,用于保存元素在编译期的各种状态
//通过roundEnv.getElementsAnnotatedWith拿到我们通过@BindView注解的元素,这里返回值,按照我们的预期应该是VariableElement集合,因为我们用于成员变量上。
Set<? extends Element> elesWithBind = roundEnv.getElementsAnnotatedWith(BindView.class); //所有被Bind注解标识的元素,此时的Element是一个通用接口
//一、收集信息
//接下来for循环我们的元素,首先检查类型是否是VariableElement(代表变量、字段)
for (Element element : elesWithBind)
{
//检查element类型
checkAnnotationValid(element, BindView.class);
//field type
VariableElement variableElement = (VariableElement) element; //因为上面检查过了,所以这里强转
//class type,拿到对应的类元素
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
//full class name,类的全路径名
String fqClassName = classElement.getQualifiedName().toString();
//如果没有生成才会去生成一个新的,。
CodeCreater codeCreater = codeCreaterMap.get(fqClassName);
if (codeCreater == null)
{
//如果对应的类还没有生成代理类,才去创建一个并且保存
codeCreater = new CodeCreater(elementUtils, classElement);
codeCreaterMap.put(fqClassName, codeCreater);
}
//接下来,会将与该类对应的且被@BindView声明的VariableElement加入到codeCreater中去,key为我们声明时填写的id,即View的id
BindView bindAnnotation = variableElement.getAnnotation(BindView.class); //获取对应的注解对象
int id = bindAnnotation.value();
//如果这个字段属于某个类,这里就会保存到同一个codeCreater中,因为不同类的时候,这里的codeCreater是不同的
codeCreater.injectVariables.put(id , variableElement);
}
//二、生成代理类
for (String key : codeCreaterMap.keySet())
{
CodeCreater codeCreater = codeCreaterMap.get(key);
try
{
JavaFileObject jfo = processingEnv.getFiler().createSourceFile(
codeCreater.getCreaterClassFullName(),
codeCreater.getTypeElement());
Writer writer = jfo.openWriter();
//写入自动生成的代码
writer.write(codeCreater.generateJavaCode());
writer.flush();
writer.close();
} catch (IOException e)
{
error(codeCreater.getTypeElement(),
"Unable to write injector for type %s: %s",
codeCreater.getTypeElement(), e.getMessage());
}
}
return true;
}
//要field字段和非private修饰才有效
private boolean checkAnnotationValid(Element annotatedElement, Class clazz)
{
if (annotatedElement.getKind() != ElementKind.FIELD)
{
error(annotatedElement, "%s must be declared on field.", clazz.getSimpleName());
return false;
}
//是否为私有的字段
if (annotatedElement.getModifiers().contains(PRIVATE))
{
error(annotatedElement, "%s() must can not be private.", annotatedElement.getSimpleName());
return false;
}
return true;
}
private void error(Element element, String message, Object... args)
{
if (args.length > 0)
{
message = String.format(message, args);
}
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message, element);
}
}
上面通过继承系统的AbstractProcessor来自己实现注解处理器,我们主要重写process方法,上面涉及到了一个很重要的接口Element(元素),这个接口有几个实现,代表字段元素、类元素、方法元素等等。就是通过这些元素,我们才能获得元素对应的注解信息。其中CodeCreater类是一个代码生成器,通过它我们可以为每一个使用注解的activity或view自动生成一个对应的ViewInject类,这个类就是给被BindView注解标识的元素赋值的。我们来看看代码:
public class CodeCreater {
private String packageName;
private String createrClassName;
private TypeElement typeElement;
//保存findViewById要用到的id值和对应的变量元素
public Map<Integer, VariableElement> injectVariables = new HashMap<>();
public static final String SUFFIX = "ViewInject";
public CodeCreater(Elements elementUtils, TypeElement classElement) {
this.typeElement = classElement;
//获取注解元素类的全包名
PackageElement packageElement = elementUtils.getPackageOf(classElement);
String packageName = packageElement.getQualifiedName().toString();
//classname
String className = classElement.getQualifiedName().toString().substring(packageName.length() + 1);
this.packageName = packageName;
this.createrClassName = className + "$$" +SUFFIX;
}
public String generateJavaCode() {
StringBuilder builder = new StringBuilder();
builder.append("/* Generated code. Do not modify!*/\n");
builder.append("package ").append(packageName).append(";\n\n"); //包路径
builder.append("import com.lib_java.compileAnnotation.*;\n"); //导入包路径
builder.append('\n');
//类的声明并且继承ViewInject<T>接口,其中T是使用注解的那个类
builder.append("public class ").append(createrClassName).append(" implements " + CodeCreater.SUFFIX +
"<" + typeElement.getQualifiedName() + ">");
builder.append(" {\n\n");
generateMethods(builder);
builder.append('\n');
builder.append("}\n");
return builder.toString();
}
private void generateMethods(StringBuilder builder) {
//参加inject方法,实际上是实现ViewInject接口的方法
builder.append(" @Override\n ");
builder.append(" public void inject(" + typeElement.getQualifiedName() + " master, Object viewOwner ) {\n");
for (int id : injectVariables.keySet()) {
VariableElement element = injectVariables.get(id); //id对应的变量
String name = element.getSimpleName().toString(); //变量名
String type = element.asType().toString(); //变量类型
//master代表使用注解的那个类的全限定名,所以master.name代表被注解的那个变量,其实也就是对应的activity对象
builder.append(" if(viewOwner instanceof android.app.Activity){\n"); //如果master是activity
builder.append(" master." + name).append(" = ");
builder.append("(" + type + ")(((android.app.Activity)viewOwner).findViewById( " + id + "));\n");
builder.append("\n }else{\n");
builder.append(" master." + name).append(" = ");
builder.append("(" + type + ")(((android.view.View)viewOwner).findViewById( " + id + "));\n"); //master是View对象
builder.append("\n }\n");
}
builder.append(" }\n");
}
public String getCreaterClassFullName() {
return packageName + "." + createrClassName;
}
public TypeElement getTypeElement() {
return typeElement;
}
}
我们可以看到,主要作用就是通过字符串的拼接去生成一个类,这个类的名字为使用者的类名+“$$ViewInject”。什么注解处理器也完成了,下面我们就暴露一个使用接口ViewInjector类,用这个类来完成最后的绑定工作,如下:
public class ViewInjector
{
private static final String SUFFIX = "$$ViewInject";
public static void injectView(Activity activity)
{
//找到自动生成的代理类
ViewInject proxyActivity = findProxyActivity(activity);
proxyActivity.inject(activity, activity); //执行注入方法,两个参数都是对应的activity对象
}
//object是持有被注解字段的对象,view是指我们要findViewById那个view对象, 也可以是activity对象
public static void injectView(Object object, View view)
{
ViewInject proxyActivity = findProxyActivity(object);
proxyActivity.inject(object, view);
}
private static ViewInject findProxyActivity(Object activity)
{
try
{
Class clazz = activity.getClass();
Class injectorClazz = Class.forName(clazz.getName() + SUFFIX);
return (ViewInject) injectorClazz.newInstance();
} catch (ClassNotFoundException e)
{
e.printStackTrace();
} catch (InstantiationException e)
{
e.printStackTrace();
} catch (IllegalAccessException e)
{
e.printStackTrace();
}
throw new RuntimeException(String.format("can not find %s , something when compiler.", activity.getClass().getSimpleName() + SUFFIX));
}
}
下面我们来测试一下
public class MainActivity extends AppCompatActivity {
@BindView(R.id.bindView)
Button BindBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewInjector.injectView(this); //调用注入
BindBtn.setText("成功绑定了view");
}
最后测试是成功通过的,最后我们来看看自动生成的那个MainActivity$$ViewInject是怎么样的,如下
/* Generated code. Do not modify!*/
package com.androidstudydata;
import com.lib_java.compileAnnotation.*;
public class MainActivity$$ViewInject implements ViewInject<com.androidstudydata.MainActivity> {
@Override
public void inject(com.androidstudydata.MainActivity master, Object viewOwner ) {
if(viewOwner instanceof android.app.Activity){
master.BindBtn = (android.widget.Button)(((android.app.Activity)viewOwner).findViewById( 2131230762));
}else{
master.BindBtn = (android.widget.Button)(((android.view.View)viewOwner).findViewById( 2131230762));
}
}
}
可以看到,其实到最后还是通过findViewById方法去给view赋值的,只是我们通过编译时的注解处理器,将这些代码自动生成了,这样就节省了我们很多时间,提高了效率。
好了,关于注解的知识就讲到这里了!附上源码: