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 "";
}
}
定义一个注解首先用@interface来声明,例子中的@Target(ElementType.METHOD)代表此注解应用在方法之上,@Retention(RetentionPolicy.RUNTIME)代表此注解的生命周期是保存到运行时,String name() default ""是一个注解元素,注解元素可以在使用的时候传值,在声明的时候用default给予一个初始值
定义注解跟定义类有点相似,如果拿类来做比较的话,我认为可以这样理解,@interface对应于类的class关键字,FunAnno对应于类的类名,大括号里面的name对应类的属性字段,和类最大的不同的是注解上面还有类似于@Target、@Retention这样的字段,其实这些也是注解,只不过它们是修饰注解的注解,我们称之为元注解,元注解是注解中重要的一部分,很大程度上定义了这个注解的属性,下面来详细解析一下什么是元注解
一:元注解
常见的元注解有:@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 {
}
//有Inherited的注解修饰父类A
@AnnoA
class A{
}
class B extends A{
}
//没有Inherited的注解修饰父类C
@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的时候,大括号内有一个String类型的元素name,这个就是注解元素。在定义注解的时候,通常都会包含一些注解元素,如下:
/**
* 定义一个注解PersonInfo,包含有三个注解元素,分别代表名字、年龄、性别
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PersonInfo{
//注解元素及其默认值
String name() default "";
int age() default -1;
boolean male() default false;
}
/**
* 使用注解,给注解元素赋值
*/
@Person.PersonInfo(name = "王大锤",age = 18,male = true)
public class Person {}
/**
* 如果注解只有一个注解元素,可使用简化方式定义:value()
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Age{
int value() default -1;
}
/**
* 注解的简化使用
*/
@Age(20)
class Lily{
}
}
上面展示了注解元素的定义及其使用,注解元素的作用可以为注解定义各种类型的变量,注解元素和元注解构成了注解的最重要两部分,元注解定义注解的性质,比如生命周期、应用范围等等,注解元素则定义了注解的属性变量
下面是注解元素支持的数据类型:
-
所有基本类型(int,float,boolean,byte,double,char,long,short)
-
String
-
Class
-
enum
-
Annotation 注解嵌套
-
上述类型的数组
下面用例子来展示所有注解元素的数据类型的使用:
public class AnnoElement {
//定义一个注解
@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 {""};
}
//嵌套的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AnnoRef{
int value() default -1;
}
/**
* 注解元素的使用
*/
@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.运行时注解
下面是在运行时通过注解来构建一个SQL语句,进而创建数据库表的例子
首先定义一个包含注解的类,如下:
/**
* 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 {
//主键,属于一个string类型的字段,然后指定是否为主键为true
@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;
//获取class对象中的Entity注解
RAnnotation.Entity entity = clazz.getAnnotation(RAnnotation.Entity.class);
String tabelName; //数据库名称
//如果没有指定数据库名称,就把类名称作为数据库名称
if (entity == null || entity.tableName() == "") {
tabelName = clazz.getSimpleName().toUpperCase();
}else{
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; //字段没有注解则不处理
}
//如果当前字段的注解是FeildInteger,说明当前字段是一个Integer类型
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;
}
/**
* 获取注解的相关约束值
*
* @param cons
* @return
*/
public static String getContrains(RAnnotation.Contrains cons) {
String constraints = "";
if (!cons.allowNull())
constraints += " NOT NULL";
if (cons.primaryKey())
constraints += " PRIMARY KEY";
if (cons.isUnique())
constraints += " UNIQUE";
return constraints;
}
我们来看一下执行结果:
成功地返回了一个正确的SQL语句!附上源码:https://github.com/jiusetian/AndroidStudyData/tree/master/app/src/main/java/com/androidstudydata/annotation
上面是一个利用运行时注解拼接SQL语句的例子,关键是利用Class对象去找到注解的相关信息,下面我来看看编译时注解的应用
2.编译时注解
编译时注解关键是利用注解处理器(Annotation Processor),注解处理器是javac内置的一个用于编译时扫描和处理注解的工具,在源代码的编译阶段,通过注解处理器,我们可以获得文件中注解的相关内容。常见的用途是,通过在获得注解相关数据之后,去生成有规律的代码,解决编程中的重复工作,大大提高了效率,比如ButterKnife、Dagger2等框架
下面我们模仿butterknife的实现原理,实现一个类似功能的简单例子,首先在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);
}
来看看注解处理器的实现,这个很关键:
@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集合,
// 因为我们用于成员变量上
//所有被Bind注解标识的元素,此时的Element是一个通用接口
Set<? extends Element> elesWithBind = roundEnv.getElementsAnnotatedWith(BindView.class);
//一、收集信息
//遍历所有被注解BindView标识的元素
for (Element element : elesWithBind) {
//检查element类型是否有效
if (!checkAnnotationValid(element, BindView.class)) continue;
//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);
}
//获取元素的注解和注解元素的值,即view的id
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int id = bindAnnotation.value();
//将元素和对应的id保存起来,其实variableElement是View,id是View的id
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(元素),这个接口有几个实现,代表字段元素、类元素、方法元素等等,可以通过Element获得元素对应的注解信息。CodeCreater是一个代码生成器,通过它可以为每一个使用注解的activity或fragment自动生成一个ViewInject类,ViewInject类就是在编译阶段自动的生成的代码了,它的作用是给标识了注解的view变量赋值,来看看CodeCreater:
package com.lib_java.compileAnnotation;
import java.util.HashMap;
import java.util.Map;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
public class CodeCreater {
private String packageName;
//自动生成类的全路径名:使用注解的类的包路径 + 使用注解的类的名称$$ViewInject
private String createrClassName;
//使用注解的类
private TypeElement typeElement;
//保存findViewById要用到View元素和对应的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;
}
/**
* 生成对应的Java代码
* @return
*/
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();
}
/**
* 生成对应方法
* @param builder
*/
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”
完成了注解处理器以后,在编译阶段就会自动生成一个初始化view变量的代理类,这个代理类大概是这样的:
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( 2131230772));
}else{
master.BindBtn = (android.widget.Button)(((android.view.View)viewOwner).findViewById( 2131230772));
}
}
}
代理类通过实现ViewInject接口,这个接口也是我们提前定义好的,如下
/**
* Created by XR_liu on 2018/11/24.
*/
public interface ViewInject<T> {
//T是指使用注解的类,viewOwner是注解view的持有者
void inject(T t, Object viewOwner);
}
inject方法是View的注入方法,是使用的时候通过调用代理类的inject方法来完成View的注入,此方法有两个参数,第一个参数是使用注解那个类的类全路径名,第二个参数是这个类的类型
最后为了方便使用,我们定义一个对外的接口ViewInjector类,通过injectView方法,调用对应代理类的注入方法inject,然后就可以对注解的View变量赋值了,如下
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");
}
成功通过,其实到最后还是通过findViewById方法给view赋值的,只是我们通过编译时的注解处理器,将这些代码自动生成了而不是自己手动去编写,这样节省了很多时间,提高了工作效率
好了,关于注解的知识就讲到这里了!附上源码: