今年你们都在搞组件化,组件化开发不可避免的须要用到路由(Router)来完成组件之间数据的交互,这就促进了各类路由发展如:阿里的ARouter以及ActivityRouter等优秀的Router框架。为了方便你们的开发这些Router库以及像ButterKnife这类的库都用到了注解技术。本篇目的是进行一波扫盲。html
Java提供了三个基本的Annotation注解,使用时须要在Annotation前面增长@符号,并把Annotation当成一个修饰符来使用。注:Java提供的基本Annotation注解都在java.lang包下面java
public class Animal {
public void run() {
//TODO
}
}
复制代码
public class Monkey extends Animal {
@Override
public void run() {
//使用了OVerride注解以后,必须重写父类方法
}
}
复制代码
public class Animal {
@Deprecated
public void run() {
//TODO
}
}
复制代码
3. @SuppressWarnings:抑制编译器警告(用的比较少)。Java代码编译时IDE每每会给开发者不少警告信息,例如变量没有使用等,这种警告多了以后很大程度上影响咱们debug效率。此注解就是来抑制这些警告。举个栗子:
@SuppressWarning("unused")
public void foo() {
String s;
}
复制代码
若是不使用@SuppressWarning来抑制编译器警告,上面的代码会被警告变量s从未使用。出了"unused",该注解支持的抑制类型还有下图的内容(注该图摘自IBM Knowledge Center)。android
JDK出了在java.lang包中提供了1.1介绍的几种基本Annotation外还在java.lang.annotation包下面提供了四个Meta Annotation(元Annotation)。这四种元Annotation都是来修饰自定义注解的。(hold住节奏,看完这个小结我们就能够自定义Annotation了)git
举个例子,自定义一个BindView注解(看不懂不要紧,现有一个感性的认识,下一节开始作自定义Annotation讲解)。github
//此注解的做用域是Class,也就是编译时
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
int id() default 0;
}
复制代码
当成员变量为value时,能够省略。也就是说上述代码能够换成 @Retention(RetentionPolicy.CLASS)
segmentfault
//此注解修饰的是属性
@Target(ElementType.FIELD)
//此注解的做用域是Class,也就是编译时
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
int id() default 0;
}
复制代码
/**
* Created by will on 2018/2/4.
*/
@Documented
//此注解修饰的是属性
@Target(ElementType.FIELD)
//此注解的做用域是Class,也就是编译时
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
}
复制代码
/**
* Created by will on 2018/2/4.
*/
@Documented
//此注解修饰的是属性
@Target(ElementType.FIELD)
//此注解的做用域是Class,也就是编译时
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
int id();
}
复制代码
@Documented
//此注解修饰的是属性
@Target(ElementType.FIELD)
//此注解的做用域是Class,也就是编译时
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
int id() default 0;
}
复制代码
根据有没有成员变量,咱们能够将Annotation划分红两种:api
注意:只定义了自定义注解没有任何效果,还须要对Annotation的信息进行提取与加工!!!缓存
上面咱们自定义了BindView注解,你是否是想直接拿到Activity中使用呢?例如:bash
而后你发现Crash了。。。这就要引入下一节的内容了,使用apt对被注解的代码进行二次加工。完成自定义Annotation后,咱们还须要知道,针对这些注解,咱们要作哪些相关的处理,这就涉及到了Annotation的解析操做。 解析Annotation,一般分为:对运行时Annotation的解析、对编译时Annotation的解析; 解析Annotation,其实就是如何从代码中找到Annotation,一般咱们的作法是:oracle
反射的解析方式,一般运用在运行时Annotation的解析。 反射是指:利用Class、Field、Method、Construct等reflect对象,获取Annotation
一般使用Runtime修饰的注解须要使用反射来配合解析
@Retention(value = RetentionPolicy.RUNTIME)
/**
* Created by will on 2018/2/4.
*/
@Documented
//此注解修饰的是属性
@Target(ElementType.FIELD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface test {
int id() default 0;
}
复制代码
public class Animal {
@BindView(id = 1000)
String a;
@Deprecated
public void run() {
//TODO
}
}
复制代码
private void testMethod() {
Class clazz = Animal.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null) {
int id = bindView.id();
Log.e("------", String.valueOf(id));
}
}
}
复制代码
APT:是一个注解处理工具 Annotation Processing Tool 做用:利用apt,咱们能够找到源代中的注解,并根据注解作相应的处理
利用APT,在编译时生成额外的代码,不会影响性能,只是影响项目构建的速度
这里咱们说一下Android中使用apt的步骤 Android中开发自定义的apt学会两个库及一个类基本就足够了
@AutoService(Processor.class)
下面会用到。JavaPoet的学习能够直接借鉴官方api,AutoService学习成本较低(只须要用里面一句代码而已,学习成本能够忽略),下面咱们重点学习一下AbstractProcessor的使用。
/**
* Created by will on 2018/2/5.
*/
public class CustomProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
}
复制代码
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Elements elementUtils = processingEnv.getElementUtils();
}
复制代码
这个工具类是用来处理源代码的,在自定义注解处理器的领域里面,Java源代码每个类型都属于一个Element,具体使用方法能够直接参考Java官方文档
package com.example; // PackageElement
public class Test { // TypeElement
private int a; // VariableElement
private Test other; // VariableElement
public Test () {} // ExecuteableElement
public void setA ( // ExecuteableElement
int newA // TypeElement
) {}
}
复制代码
例如,我有一个TypeElement,但愿拿到这个class所在的包名就可使用Elemnts这个工具
private String getPackageName(TypeElement type) {
return elementUtils.getPackageOf(type).getQualifiedName().toString();
}
复制代码
再来一个栗子,有一个表明Test的TypeElement,但愿获取全部的子元素能够这么写(注意,这个颇有用)
TypeElement testClass = ... ;
for (Element e : testClass.getEnclosedElements()){ // iterate over children
Element parent = e.getEnclosingElement(); // parent == testClass
}
复制代码
好了枯燥的基础知识看完了以后咱们一块儿写一个简单的ButterKnife
@Documented
//此注解修饰的是属性
@Target(ElementType.FIELD)
//此注解的做用域是Class,也就是编译时
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
int id() default 0;
}
复制代码
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.7.0'
复制代码
/**
* Created by will on 2018/2/4.
*/
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
/**
* 工具类,能够从init方法的ProcessingEnvironment中获取
*/
private Elements elementUtils;
/**
* 缓存全部子Element
* key:父Element类名
* value:子Element
*/
private HashMap<String, List<Element>> cacheElements = null;
/**
* 缓存全部父Element
* key:父Element类名
* value:父Element
*/
private HashMap<String, Element> cacheAllParentElements = null;
@Override
public Set<String> getSupportedAnnotationTypes() {
// 规定须要处理的注解类型
return Collections.singleton(BindView.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> annotations
, RoundEnvironment roundEnv) {
//扫描全部注解了BindView的Field,由于咱们全部注解BindView的地方都是一个Activity的成员
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
//将全部子elements进行过滤
addElementToCache(element);
}
if (cacheElements == null || cacheElements.size() == 0) {
return true;
}
for (String parentElementName : cacheElements.keySet()) {
//判断一下获取到的parent element是不是类
try {
//使用JavaPoet构造一个方法
MethodSpec.Builder bindViewMethodSpec = MethodSpec.methodBuilder("bindView")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(ClassName.get(cacheAllParentElements.get(parentElementName).asType())
, "targetActivity");
List<Element> childElements = cacheElements.get(parentElementName);
if (childElements != null && childElements.size() != 0) {
for (Element childElement : childElements) {
BindView bindView = childElement.getAnnotation(BindView.class);
//使用JavaPoet对方法内容进行添加
bindViewMethodSpec.addStatement(
String.format("targetActivity.%s = (%s) targetActivity.findViewById(%s)"
, childElement.getSimpleName()
, ClassName.get(childElement.asType()).toString()
, bindView.id()));
}
}
//构造一个类,以Bind_开头
TypeSpec typeElement = TypeSpec.classBuilder("Bind_"
+ cacheAllParentElements.get(parentElementName).getSimpleName())
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(bindViewMethodSpec.build())
.build();
//进行文件写入
JavaFile javaFile = JavaFile.builder(
getPackageName((TypeElement) cacheAllParentElements.get(parentElementName))
, typeElement).build();
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
return true;
}
}
return true;
}
private String getPackageName(TypeElement type) {
return elementUtils.getPackageOf(type).getQualifiedName().toString();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 缓存父Element对应的全部子Element
* 缓存父Element
*
* @param childElement
*/
private void addElementToCache(Element childElement) {
if (cacheElements == null) {
cacheElements = new HashMap<>();
}
if (cacheAllParentElements == null) {
cacheAllParentElements = new HashMap<>();
}
//父Element类名
String parentElementName = null;
parentElementName = ClassName.get(childElement.getEnclosingElement().asType()).toString();
if (cacheElements.containsKey(parentElementName)) {
List<Element> childElements = cacheElements.get(parentElementName);
childElements.add(childElement);
} else {
ArrayList<Element> childElements = new ArrayList<>();
childElements.add(childElement);
cacheElements.put(parentElementName, childElements);
cacheAllParentElements.put(parentElementName, childElement.getEnclosingElement());
}
}
}
复制代码
注意:Android Gradle插件2.2版本发布后,Android 官方提供了annotationProcessor来代替android-apt,annotationProcessor同时支持 javac 和 jack 编译方式,而android-apt只支持 javac 方式。同时android-apt做者宣布不在维护,这里我直接用了annotationProcessor
implementation project(':annotations')
annotationProcessor project(':annotations_compiler')
复制代码
public class MainActivity extends AppCompatActivity {
@BindView(id = R.id.tv_test)
TextView tv_test;
@BindView(id = R.id.tv_test1)
TextView tv_test1;
@BindView(id = R.id.iv_image)
ImageView iv_image;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Bind_MainActivity.bindView(this);
tv_test.setText("test_1");
tv_test1.setText("test_2");
iv_image.setImageDrawable(getDrawable(R.mipmap.ic_launcher));
}
}
复制代码
contact way | value |
---|---|
weixinjie1993@gmail.com | |
W2006292 | |
github | https://github.com/weixinjie |
blog | https://juejin.im/user/57673c83207703006bb92bf6 |