一 注解
1.1 注解分类
注解分为标准注解和元注解,下面分别介绍它们。
1. 标准注解,标准注解有以下4种。
•@Override:对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编
2. 元注解,除了标准注解,还有元注解,它用来注解其他注解,从而创建新的注解。元注解有以下几种。
•@Targe:注解所修饰的对象范围。
•@Inherited: 标记这个注解是继承于哪个注解类
•@Documented:表示这个注解应该被JavaDoc工具记录(即该注解包含在用户文档中)。
•@Retention:用来声明注解的保留策略。
1.2 定义注解
1.基本定义
定义新的注解类型使用@interface关键字,这与定义一个接口很像,如下所示:
public @interface AnnotationDemo{
......
}
定义完注解后,就可以在程序中使用该注解:
@AnnotationDemo
public class AnnotationTest{
......
}
public @interface AnnotationDemo{
String name();
int length();
}
public class AnnotationTest{
@AnnotationDemo(naem = "我是注解名字",length = 6)
public void test(){
......
}
}
public @interface AnnotationDemo{
String name() default "我是注解名字";
int length() default 6;
}
public class AnnotationTest{
@AnnotationDemo
public void test(){
......
}
}
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDemo {
String name() default "我是注解名字";
int length() default 6;
}
@Retention(RetentionPolicy.CLASS)
public @interface AnnotationDemo {
String name() default "我是注解名字";
int length() default 6;
}
同样地,如果将@Retention的保留策略设定为RetentionPolicy.SOURCE,这个注解就是编译时注解,如下 所示:
@Retention(RetentionPolicy.SOURCE)
public @interface AnnotationDemo {
String name() default "我是注解名字";
int length() default 6;
}
1.3 注解处理器
@Documented
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Get {
String value() default "";
}
上面的代码定了@Get注解,使用了@Target(ElementType.METHOD),意味着Get注解应用于方法。接下来应用该注解,如下所示:
public class AnnotationTest {
@Get(value = "欢迎学习Java注解")
public String getMessage() {
return "";
}
}
public class AnnotationProcessor {
public static void processor(){
Method[] methods = AnnotationTest.class.getDeclaredMethods();
for (Method method:methods) {
Get get = method.getAnnotation(Get.class);
if(get!=null){
System.out.println("get.value() = " + get.value());
}
}
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AnnotationProcessor.processor();
}
}
输出结果如下:
@Documented
@Target(value = ElementType.FIELD)
@Retention(value = RetentionPolicy.CLASS)
public @interface BindView {
int value() default 1;
}
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(path: ':annotaions')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
public class ClassProcessor 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() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(BindView.class.getCanonicalName());
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
@SupportedAnnotationTypes(value = "com.lx.annotaions.BindView")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ClassProcessor 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 boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Messager messager = processingEnv.getMessager();
for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
if (element.getKind() == ElementKind.FIELD) {
messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:"
+ element.toString());
}
}
return false;
}
dependencies {
......
implementation('com.google.auto.service:auto-service:1.0-rc2')
}
@AutoService(Processor.class)
public class ClassProcessor extends AbstractProcessor {
......
}
dependencies {
......
implementation project(path: ':processor')
implementation project(path: ':annotaions')
}
接下来在MainActivity中应用注解,如下所示:
public class MainActivity extends AppCompatActivity {
@BindView(value = R.id.tv_text)
TextView tv_text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
二 注解搭配Javapoet在Android的应用
这里通过ButterKinfe的@BindView的使用做讲解,
1. 使用运行时注解的方式
(1)定义@BindView注解,代码如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value();
}
(2)应用注解,在MainActivity中使用注解,代码如下
public class MainActivity extends AppCompatActivity {
@BindView(value = R.id.tv_text)
TextView tv_text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
(3)定义注解处理器,代码如下
public class ButterKnife {
public static void bind(@Nullable Activity activity){
Field[] fields = activity.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if(field.isAnnotationPresent(BindView.class)){
BindView bindView = field.getAnnotation(BindView.class);
int resId = bindView.value();
View view = activity.findViewById(resId);
field.setAccessible(true);
Class<?> targetType = field.getType();
Class<? extends View> viewType = view.getClass();
if (!targetType.isAssignableFrom(viewType)) {
continue;
}
try {
field.set(activity, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
(4)使用,在MainActivity中调用注解处理器完成View的绑定,代码如下
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
tv_text.setText("欢迎学习Java注解");
}
运行程序之后,屏幕显示“欢迎学习Java注解”,并没有报tv_text的空指针异常,说明绑定成功。
由于是运行时处理注解,而且需要用到反射,导致效率不高,因此建议使用编译时注解,下面讲解如果使用编译时注解完成View的绑定。
2. 使用编译时注解的方式,
此处代码接着第一节中第三小节中“2.编译时注解处理器”的代码接着往下研究,
(1)在annotations module 中添加ViewBinder接口,代码如下
public interface ViewBinder<T> {
void bind(T target);
}
(2)在processor module 中修改ClassProcessor类中的process方法,代码如下
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 输出打印工具
Messager messager = processingEnv.getMessager();
// 拿到整个模块中(app)用到BindView的注解的节点
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
// key -- activity名字
Map<String, List<VariableElement>> map = new HashMap<>();
for (Element element : elementsAnnotatedWith) {
if (element.getKind() == ElementKind.FIELD) {
// 获取到成员变量的节点,也就是控件
VariableElement variableElement = (VariableElement) element;
// 获取到activity名字
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:" + activityName);
List<VariableElement> variableElements = map.get(activityName);
if (variableElements == null) {
variableElements = new ArrayList<>();
map.put(activityName, variableElements);
}
variableElements.add(variableElement);
}
}
if (map.size() > 0) {
Writer writer = null;
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String activityName = iterator.next();
// 得到的是activity对应的控件
List<VariableElement> variableElements = map.get(activityName);
// 通过空间的成员变量节点,获取到他的上一个节点,也就是类节点
Element enclosingElement = variableElements.get(0).getEnclosingElement();
// 通过类节点获取到包名
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
try {
// 生成文件的对象
Filer filer = processingEnv.getFiler();
// 生成java文件
JavaFileObject sourceFile = filer.createSourceFile(packageName + "."
+ activityName + "_ViewBinding");
writer = sourceFile.openWriter();
writer.write("package " + packageName + ";\n");
writer.write("import " + packageName + "." + activityName + ";\n");
writer.write("import " + ViewBinder.class.getName() + ";\n");
writer.write("public class " + activityName + "_ViewBinding<T extends " + activityName +
"> implements ViewBinder<" + activityName + ">{\n");
writer.write(" public void bind(" + activityName + " target){\n");
// 遍历所有成员变量 添加代码
for (VariableElement variableElement : variableElements) {
// 获取到控件的名字
String variableName = variableElement.getSimpleName().toString();
// 获取到控件的id
int resId = variableElement.getAnnotation(BindView.class).value();
// 获取到这个控件的类型
TypeMirror typeMirror = variableElement.asType();
writer.write(" target." + variableName + "=(" + typeMirror + ")target.findViewById(" + resId + ");\n");
}
writer.write(" }\n}\n");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return false;
}
然后,我们先Clean Project再Make Project,会生成MainAcitivty_ViewBinding.java文件,内容如下图
(3)在app module中创建ButterKnife.java 文件,调用我们生成的MainActivity_ViewBinding中的bind方法,完成View的绑定,代码如下
public class ButterKnife {
public static void bind(@Nullable Activity activity) {
String name = activity.getClass().getName() + "_ViewBinding";
try {
Class<?> clazz = Class.forName(name);
ViewBinder binder = (ViewBinder)clazz.newInstance();
binder.bind(activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
(4)最后在MainActivity中使用@BindView注解,然后调用ButterKnife的bind方法,代码如下
public class MainActivity extends AppCompatActivity {
@BindView(value = R.id.tv_text)
TextView tv_text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
tv_text.setText("欢迎学习Java注解");
}
}
运行程序之后,屏幕显示“欢迎学习Java注解”,并没有报tv_text的空指针异常,说明绑定成功。
在这个过程中,我们使用的Filer通过拼接的形式创建的MainActivity_ViewBinding.java文件,这里很容易出错,所以我们接下来使用Javapoet来完成MainActivity_ViewBinding.java文件的生成。
3. 使用Javapoet完成代码生成
JavaPoet
是一个动态生成代码的开源项目,在某些情况下具有特殊用处。Github
地址:https://github.com/square/javapoet
在Github
上有JavaPoet
的官方教程,权威且全面,因为太好了
Javapoet的优点
- JavaPoet是一款可以自动生成Java文件的第三方依赖
- 简洁易懂的API,上手快
- 让繁杂、重复的Java文件,自动化生成,提高工作效率,简化流程,不容易出错
(1) Javapoet的使用,在processor module的build.gradle中添加依赖,代码如下
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(path: ':annotaions')
implementation('com.google.auto.service:auto-service:1.0-rc2')
// 引入生成代码的库
implementation 'com.squareup:javapoet:1.11.1'
}
(2)在processor module 中修改ClassProcessor类中的process方法,代码如下
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 输出打印工具
Messager messager = processingEnv.getMessager();
// 拿到整个模块中(app)用到BindView的注解的节点
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
// key -- activity名字
Map<String, List<VariableElement>> map = new HashMap<>();
for (Element element : elementsAnnotatedWith) {
if (element.getKind() == ElementKind.FIELD) {
// 获取到成员变量的节点,也就是控件
VariableElement variableElement = (VariableElement) element;
// 获取到activity名字
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:" + activityName);
List<VariableElement> variableElements = map.get(activityName);
if (variableElements == null) {
variableElements = new ArrayList<>();
map.put(activityName, variableElements);
}
variableElements.add(variableElement);
}
}
if (map.size() > 0) {
Writer writer = null;
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String activityName = iterator.next();
// 得到的是activity对应的控件
List<VariableElement> variableElements = map.get(activityName);
// 通过空间的成员变量节点,获取到他的上一个节点,也就是类节点
Element enclosingElement = variableElements.get(0).getEnclosingElement();
// 通过类节点获取到包名
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
ClassName activityClassName = ClassName.bestGuess(activityName);
ClassName viewBuild = ClassName.get(ViewBinder.class.getPackage().getName(), ViewBinder.class.getSimpleName());
//构造一个方法
MethodSpec.Builder method = MethodSpec.methodBuilder("bind") //名称
.addModifiers(Modifier.PUBLIC) //修饰
.returns(void.class) //返回
.addAnnotation(Override.class)
.addParameter(activityClassName, "target", Modifier.FINAL); //参数
// 遍历所有成员变量 添加方法体
for (VariableElement variableElement : variableElements) {
// 获取到控件的名字
String variableName = variableElement.getSimpleName().toString();
// 获取到控件的id
int resId = variableElement.getAnnotation(BindView.class).value();
// 获取到这个控件的类型
TypeMirror typeMirror = variableElement.asType();
method.addStatement("target.$L=($L)target.findViewById($L)",variableName,typeMirror,resId);
}
//构造一个类
TypeSpec Class_ViewBinding = TypeSpec.classBuilder(activityName + "_ViewBinding") //名称
.addModifiers(Modifier.PUBLIC) //修饰
.addMethod(method.build()) //方法
.addTypeVariable(TypeVariableName.get("T",activityClassName))
.addSuperinterface(ParameterizedTypeName.get(viewBuild,activityClassName))
.build();
//生成一个Java文件
JavaFile javaFile = JavaFile.builder(packageName, Class_ViewBinding)
.build();
try {
//将java写到当前项目中
javaFile.writeTo(System.out); //打印到命令行中
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
然后,我们先Clean Project再Make Project,会生成MainAcitivty_ViewBinding.java文件,内容如下图
后续操作跟用Filer生成MainAcitivty_ViewBinding.java文件一样了,这里就不在说明了。