写在开头:最近在翻读一些开源库的时候,发现大多使用了注解,因而不得不来仔细了解一下Android下的注解知识java
java.lang.annotation
,接口 Annotation,在JDK5.0及之后版本引入。android
注解是代码里的特殊标记,这些标记能够在编译、类加载、运行时被读取,并执行相应的处理。经过使用Annotation,开发人员能够在不改变原有逻辑的状况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具能够经过这些补充信息进行验证、处理或者进行部署。git
Annotation不能运行,它只有成员变量,没有方法。Annotation跟public、final等修饰符的地位同样,都是程序元素的一部分,Annotation不能做为一个程序元素使用。github
注解将一些原本重复性的工做,变成程序自动完成,简化和自动化该过程。好比用于生成Java doc,好比编译时进行格式检查,好比自动生成代码等,用于提高软件的质量和提升软件的生产效率。json
Android已经定义好的注解大体分为4种,称之为4大元注解api
@Retention:定义该Annotation被保留的时间长度app
RetentionPoicy.SOURCE
:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;用于作一些检查性的操做,好比 @Override
和 @SuppressWarnings
RetentionPoicy.CLASS:
注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期;用于在编译时进行一些预处理操做,好比生成一些辅助代码(如 ButterKnife
)RetentionPoicy.RUNTIME:
注解不只被保存到class文件中,jvm加载class文件以后,仍然存在;用于在运行时去动态获取注解信息。这个注解大都会与反射一块儿使用@Target:定义了Annotation所修饰的对象范围框架
ElementType.CONSTRUCTOR
:用于描述构造器ElementType.FIELD
:用于描述域ElementType.LOCAL_VARIABLE
:用于描述局部变量ElementType.METHOD
:用于描述方法ElementType.PACKAGE
:用于描述包ElementType.PARAMETER
:用于描述参数ElementType.TYPE
:用于描述类、接口(包括注解类型) 或enum声明未标注则表示可修饰全部jvm
@Inherited:是否容许子类继承父类的注解,默认是falseide
@Documented 是否会保存到 Javadoc 文档中
自定义注解中使用到较多的是运行时注解和编译时注解
运行时注解
下面经过一个简单的动态绑定控件的例子来讲明
首先定义一个简单的自定义注解,
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value() default -1;
}
复制代码
而后在app运行时,经过反射将findViewbyId()获得的控件,注入到咱们须要的变量中。
public class AnnotationActivity extends AppCompatActivity {
@BindView(R.id.annotation_tv)
private TextView mTv;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_annotation);
getAllAnnotationView();
mTv.setText("Annotation");
}
private void getAllAnnotationView() {
//得到成员变量
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
try {
//判断注解
if (field.getAnnotations() != null) {
//肯定注解类型
if (field.isAnnotationPresent(BindView.class)) {
//容许修改反射属性
field.setAccessible(true);
BindView bindView = field.getAnnotation(BindView.class);
//findViewById将注解的id,找到View注入成员变量中
field.set(this, findViewById(bindView.value()));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
复制代码
最后mTv
上显示的就是咱们想要的“Annotation”文字,这看起来是否是有点像ButterKnife
,可是要注意反射是很消耗性能的,
因此咱们经常使用的控件绑定库ButterKnife
并非采用运行时注解,而是采用的编译时注解.
编译时注解
定义
在说编译时注解以前,咱们得先提一提注解处理器AbstractProcessor
。
它是javac的一个工具,用来在编译时扫描和处理注解Annotation
,你能够自定义注解,并注册到相应的注解处理器,由注解处理器来处理你的注解。
一个注解的注解处理器,以Java代码(或者编译过的字节码)做为输入,生成文件(一般是.java文件)做为输出。这些由注解器生成的.java代码和普通的.java同样,能够被javac编译。
导入
由于AbstractProcessor
是javac中的一个工具,因此在Android的工程下无法直接调用。下面提供一个本人尝试可行的导入方式。
File-->New Module-->java library 新建一个java module,注意必定要是java library,不是Android library
接下来就能够在对应的library中使用AbstractProcessor
了
准备工做完成以后,下面经过一个简单的注解绑定控件的例子来说述
工程目录
--app (主工程)
--app_annotation (java module 自定义注解)
--annotation-api (Android module)
--app_compiler (java module 注解处理器逻辑)
复制代码
在annotation module下建立注解
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
//绑定控件
int value();
}
复制代码
在compiler module下建立注解处理器 CustomProcessor
public class CustomProcessor extends AbstractProcessor {
//文件相关的辅助类
private Filer mFiler;
//元素相关的辅助类
private Elements mElements;
//初始化参数
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mElements = processingEnvironment.getElementUtils();
mFiler = processingEnvironment.getFiler();
}
//核心处理逻辑,至关于java中的主函数main(),你须要在这里编写你本身定义的注解的处理逻辑
//返回值 true时表示当前处理,不容许后续的注解器处理
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
return true;
}
//自定义注解集合
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
复制代码
其中核心代码process
函数有两个参数,咱们重点关注第二个参数,由于env
表示的是全部注解的集合
首先咱们先简单的说明一下porcess
的处理流程
- 遍历env,获得咱们须要的元素列表
for(Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)){
// todo ....
// 判断元素的类型为Class
if (element.getKind() == ElementKind.CLASS) {
// 显示转换元素类型
TypeElement typeElement = (TypeElement) element;
// 输出元素名称
System.out.println(typeElement.getSimpleName());
// 输出注解属性值
System.out.println(typeElement.getAnnotation(BindView.class).value());
}
}
复制代码
直接经过getElementsAnnotatedWith
函数就能获取到须要的注解的列表,函数体内加了些element
简单的使用
2.将元素列表封装成对象,方便以后的处理
首先,咱们须要明确,在绑定控件的这个事件下,咱们须要的是控件的id。
新建类 BindViewField.class
用来保存自定义注解BindView
相关的属性
BindViewField.class
public class BindViewField {
private VariableElement mFieldElement;
private int mResId;
public BindViewField(Element element) throws IllegalArgumentException {
if (element.getKind() != ElementKind.FIELD) {
throw new IllegalArgumentException(String.format("Only field can be annotated with @%s",
BindView.class.getSimpleName()));
}
mFieldElement = (VariableElement) element;
BindView bindView = mFieldElement.getAnnotation(BindView.class);
mResId = bindView.value();
if (mResId < 0) {
throw new IllegalArgumentException(String.format("value() in %s for field % is not valid",
BindView.class.getSimpleName(), mFieldElement.getSimpleName()));
}
}
public Name getFieldName() {
return mFieldElement.getSimpleName();
}
public int getResId() {
return mResId;
}
public TypeMirror getFieldType() {
return mFieldElement.asType();
}
}
复制代码
上述的BindViewField
只能表示一个自定义注解bindView
对象,而一个类中极可能会有多个自定义注解,因此还须要建立一个对象Annotation.class
来管理自定义注解集合、
AnnotatedClass.class
public class AnnotatedClass {
//类
public TypeElement mClassElement;
//类内的注解变量
public List<BindViewField> mFiled;
//元素帮助类
public Elements mElementUtils;
public AnnotatedClass(TypeElement classElement, Elements elementUtils) {
this.mClassElement = classElement;
this.mElementUtils = elementUtils;
this.mFiled = new ArrayList<>();
}
//添加注解变量
public void addField(BindViewField field) {
mFiled.add(field);
}
//获取包名
public String getPackageName(TypeElement type) {
return mElementUtils.getPackageOf(type).getQualifiedName().toString();
}
//获取类名
private static String getClassName(TypeElement type, String packageName) {
int packageLen = packageName.length() + 1;
return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');
}
}
复制代码
给上完整的解析流程
//解析事后的目标注解集合
private Map<String, AnnotatedClass> mAnnotatedClassMap = new HashMap<>();
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mAnnotatedClassMap.clear();
try {
processBindView(roundEnvironment);
} catch (Exception e) {
e.printStackTrace();
return true;
}
return true;
}
private void processBindView(RoundEnvironment env) {
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
AnnotatedClass annotatedClass = getAnnotatedClass(element);
BindViewField field = new BindViewField(element);
annotatedClass.addField(field);
System.out.print("p_element=" + element.getSimpleName() + ",p_set=" + element.getModifiers());
}
}
private AnnotatedClass getAnnotatedClass(Element element) {
TypeElement encloseElement = (TypeElement) element.getEnclosingElement();
String fullClassName = encloseElement.getQualifiedName().toString();
AnnotatedClass annotatedClass = mAnnotatedClassMap.get(fullClassName);
if (annotatedClass == null) {
annotatedClass = new AnnotatedClass(encloseElement, mElements);
mAnnotatedClassMap.put(fullClassName, annotatedClass);
}
return annotatedClass;
}
复制代码
3.经过JavaPoet库将对象以咱们指望的形式生成java文件
经过上述两步成功获取了自定义注解的元素对象,可是仍是缺乏一步关键的步骤,缺乏一步findViewById()
,实际上ButterKnife这个很出名的库也并无省略findViewById()
这一个步骤,只是在编译的时候,在build/generated/source/apt/debug下生成了一个文件,帮忙执行了findViewById()
这一行为而已。
一样的,咱们这里也须要生成一个java文件,采用的是JavaPoet这个库。具体的使用 参考连接
在process
函数中增长生成java文件的逻辑
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mAnnotatedClassMap.clear();
try {
processBindView(roundEnvironment);
} catch (Exception e) {
e.printStackTrace();
return true;
}
try {
for (AnnotatedClass annotatedClass : mAnnotatedClassMap.values()) {
annotatedClass.generateFinder().writeTo(mFiler);
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
复制代码
其中核心逻辑annotatedClass.generateFinder().writeTo(mFiler);
具体实如今AnnotatedClass
中
public JavaFile generateFinder() {
//构建 inject 方法
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(TypeName.get(mClassElement.asType()), "host", Modifier.FINAL)
.addParameter(TypeName.OBJECT, "source")
.addParameter(Utils.FINDER, "finder");
//inject函数内的核心逻辑,
// host.btn1=(Button)finder.findView(source,2131427450); ----生成代码
// host.$N=($T)finder.findView(source,$L) ----原始代码
// 对比就会发现这里执行了实际的findViewById绑定事件
for (BindViewField field : mFiled) {
methodBuilder.addStatement("host.$N=($T)finder.findView(source,$L)", field.getFieldName()
, ClassName.get(field.getFieldType()), field.getResId());
}
String packageName = getPackageName(mClassElement);
String className = getClassName(mClassElement, packageName);
ClassName bindClassName = ClassName.get(packageName, className);
//构建类对象
TypeSpec finderClass = TypeSpec.classBuilder(bindClassName.simpleName() + "$$Injector")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(Utils.INJECTOR, TypeName.get(mClassElement.asType()))) //继承接口
.addMethod(methodBuilder.build())
.build();
return JavaFile.builder(packageName, finderClass).build();
}
复制代码
到这里,大部分逻辑都已实现,用来绑定控件的辅助类也已通关JavaPoet生成了,只差最后一步,宿主注册,如同ButterKnife通常,ButterKnife.bind(this)
编写调用接口
在annotation-api下新建
注入接口Injector
public interface Injector<T> {
void inject(T host, Object source, Finder finder);
}
复制代码
宿主通用接口Finder
(方便以后扩展到view和fragment)
public interface Finder {
Context getContext(Object source);
View findView(Object source, int id);
}
复制代码
activity实现类 ActivityFinder
public class ActivityFinder implements Finder{
@Override
public Context getContext(Object source) {
return (Activity) source;
}
@Override
public View findView(Object source, int id) {
return ((Activity) (source)).findViewById(id);
}
}
复制代码
核心实现类 ButterKnife
public class ButterKnife {
private static final ActivityFinder finder = new ActivityFinder();
private static Map<String, Injector> FINDER_MAP = new HashMap<>();
public static void bind(Activity activity) {
bind(activity, activity);
}
private static void bind(Object host, Object source) {
bind(host, source, finder);
}
private static void bind(Object host, Object source, Finder finder) {
String className = host.getClass().getName();
try {
Injector injector = FINDER_MAP.get(className);
if (injector == null) {
Class<?> finderClass = Class.forName(className + "$$Injector");
injector = (Injector) finderClass.newInstance();
FINDER_MAP.put(className, injector);
}
injector.inject(host, source, finder);
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
对应的按钮能够直接使用,不须要findViewById()
public class MainActivity extends AppCompatActivity {
@BindView(R.id.annotation_tv)
public TextView tv1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
tv1.setText("annotation_demo");
}
}
复制代码
经常使用的几个类
经常使用的占位符
$L for variable (变量)
$S for Strings
$T for Types
$N for Names(咱们本身生成的方法名或者变量名等等)
自定义Processor
注解处理器中最主要的处理方法是process()
函数,而process()
函数中重要的是 RoundEnvironment
参数,
一般的使用方式
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
//todo
}
复制代码
经过BindView注解获取全部的Element对象,而这个Element是什么呢?
Element表示一个程序元素,能够是包,类或者是方法,全部经过注解取到的元素都将以Element类型处理.准确的来讲是Element对象的子类处理。
@Target(ElementType.METHOD)
和 @Target(ElementType.CONSTRUCTOR)
@Target(ElementType.PACKAGE)
@Target(ElementType.TYPE)
@Target(ElementType.PARAMETER)
@Target(ElementType.LOCAL_VARIABLE)
修饰方法的注解和ExecutableElement
当你有一个注解是以@Target(ElementType.METHOD)定义时,表示该注解只能修饰方法。
获取咱们须要的一些基本信息
//BindClick.class 以 @Target(ElementType.METHOD)修饰
for (Element element : roundEnv.getElementsAnnotatedWith(BindClick.class)) {
//对于Element直接强转
ExecutableElement executableElement = (ExecutableElement) element;
//非对应的Element,经过getEnclosingElement转换获取
TypeElement classElement = (TypeElement) element.getEnclosingElement();
//当(ExecutableElement) element成立时,使用(PackageElement) element.getEnclosingElement();将报错。
//须要使用elementUtils来获取
Elements elementUtils = processingEnv.getElementUtils();
PackageElement packageElement = elementUtils.getPackageOf(classElement);
//全类名
String fullClassName = classElement.getQualifiedName().toString();
//类名
String className = classElement.getSimpleName().toString();
//包名
String packageName = packageElement.getQualifiedName().toString();
//方法名
String methodName = executableElement.getSimpleName().toString();
//取得方法参数列表
List<? extends VariableElement> methodParameters = executableElement.getParameters();
//参数类型列表
List<String> types = new ArrayList<>();
for (VariableElement variableElement : methodParameters) {
TypeMirror methodParameterType = variableElement.asType();
if (methodParameterType instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) methodParameterType;
methodParameterType = typeVariable.getUpperBound();
}
//参数名
String parameterName = variableElement.getSimpleName().toString();
//参数类型
String parameteKind = methodParameterType.toString();
types.add(methodParameterType.toString());
}
}
复制代码
修饰属性、类成员的注解和VariableElement
for (Element element : roundEnv.getElementsAnnotatedWith(IdProperty.class)) {
//ElementType.FIELD注解能够直接强转VariableElement
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) element
.getEnclosingElement();
PackageElement packageElement = elementUtils.getPackageOf(classElement);
//类名
String className = classElement.getSimpleName().toString();
//包名
String packageName = packageElement.getQualifiedName().toString();
//类成员名
String variableName = variableElement.getSimpleName().toString();
//类成员类型
TypeMirror typeMirror = variableElement.asType();
String type = typeMirror.toString();
}
复制代码
修饰类的注解和TypeElement
for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
//ElementType.TYPE注解能够直接强转TypeElement
TypeElement classElement = (TypeElement) element;
PackageElement packageElement = (PackageElement) element
.getEnclosingElement();
//全类名
String fullClassName = classElement.getQualifiedName().toString();
//类名
String className = classElement.getSimpleName().toString();
//包名
String packageName = packageElement.getQualifiedName().toString();
//父类名
String superClassName = classElement.getSuperclass().toString();
}
复制代码