注解的介绍
注解介绍
注解是在 Java SE5 引入进来的。
注解又称为标注,用于为代码提供元数据。 作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。可以作用在类、方法、变量、参数和包等上。 你可以通俗的理解成“标签”,这个标签可以标记类、方法、变量、参数和包。
注解作用
注解单独存在时是没有意义的,需要与注解处理器一起,才能起作用
- 注解+APT,用于生成一些Java 文件
- 注解+代码埋点,用户做日志手机统计等
- 注解+反射,用于为View 组件 增加事件监听等
Java 元注解
名字 | 描述 |
---|---|
@Retention | 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问 |
@Documented | 标记这些注解是否包含在用户文档中,即包含到 Javadoc 中去 |
@Target | 标记这个注解的作用目标 |
@Inherited | 标记这个注解是继承于哪个注解类 |
@Repeatable | Java 8 开始支持,标识某注解可以在同一个声明上使用多次 |
@Retention
表示注解保留时间长短。可选的参数值在枚举类型 java.lang.annotation.RetentionPolicy
中,取值为:
- RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视,不会写入 class 文件;
- RetentionPolicy.CLASS:注解只被保留到编译进行的时候,会写入 class 文件,它并不会被加载到 JVM 中;
- RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以反射获取到它们。
@Target
用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。 可能的值在枚举类 java.lang.annotation.ElementType
中,包括:
ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上;
ElementType.FIELD:允许作用在属性字段上;
ElementType.METHOD:允许作用在方法上;
ElementType.PARAMETER:允许作用在方法参数上;
ElementType.CONSTRUCTOR:允许作用在构造器上;
ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上;
ElementType.ANNOTATION_TYPE:允许作用在注解上;
ElementType.PACKAGE:允许作用在包上。
@Target
注解的参数也可以接收一个数组,表示可以作用在多种目标类型上,如:@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
注解+APT 实战:简单实现ButterKnife框架
APT是注解处理器,是一种用来处理注解的工具。JVM会在编译期就运行APT去扫描处理代码中的注解。我们在拿到对应的注解时,可以生成我们需要的模板代码
在获取View空间的时候,我们往往需要通过findViewById
来拿到控件的示例,会比较繁琐,我们可以通过APT的核心原理,来生成findViewById
的模板代码,这样就不需要在每个Activity中都通过这个方式去执行。
核心原理:
- 声明控件变量,并添加
BindView
注解,绑定控件对应的ID - 在apt注解处理器,处理
BindView
注解,生成对应的模板代码Java文件(用到Writer
去生成文件) - 调用模板代码,实现
findViewById
功能
实现步骤
1. 创建annotation库
创建一个java-library
库,命名为:annotation。这个库用来存放自定义注解。
在此库中创建一个注解BindView
BindView
代码如下:
@Retention(RetentionPolicy.CLASS)//编译时起效
@Target(ElementType.FIELD)//针对的是属性
public @interface BindView {
int value(); //定义输入参数为整形,例如:@BindView(R.id.xxx)
}
2. 创建annotation_compiler库
此库是APT的核心处理库,用来在编译时生成处模板代码
注意: 这也是一个
java-library
库,继承的 AbstractProcessor 在 javax包下才能引入
在此之前,我们需要在App
模块中定义一个IBinder
接口,让即将生成的模板类继承此接口
public interface IBinder<T> {
void bind(T target);
}
这个接口的作用是,当我们通过反射的方式生成模板类实例,直接调用bind方法来实现View绑定
String name = activity.getClass().getName() + "_ViewBinding";//这个是模板类的类名
try {
Class<?> aClass = Class.forName(name);
IBinder iBinder = (IBinder) aClass.newInstance();//生成模板类实列
iBinder.bind(activity);//实现findViewbyId功能
} catch (Exception e) {
e.printStackTrace();
}
注意:记住这个IBinder所在包目录,下面生成模板时用到
创建完成后,在build.gradle中,引入如下:
plugins {
id 'java-library'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
dependencies {
compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7' //Google 注解处理器
implementation project(path: ':annotation') //引用刚刚创建的注解
}
定义处理器代码 BindViewProcessor.java
,这个处理器的目的是生成如下模板代码:
package com.example.annotation;
import com.example.annotation.IBinder;
public class MainActivity_ViewBinding implements IBinder<com.example.annotation.MainActivity> {
@Override
public void bind(com.example.annotation.MainActivity target) {
target.mTvText = (android.widget.TextView) target.findViewById(2131231186);
}
}
以上的 IBinder
就是我们在App中创建的,在com.example.annotation
包下面,我们需要一点点将以上目标代码拼装在一起
所有代码处理,都在 process
方法中实现
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private static final String TAG = BindViewProcessor.class.getSimpleName();
//1.支持的版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//2.能用来处理哪些注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
//3.定义一个用来生成APT目录下面的文件的对象
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//filter 用于后续写文件
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//打印测试
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "===>" + set);
//获取APP中所有用到了BindView注解的对象
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
// TypeElement -->类
// ExecutableElement --> 方法
// VariableElement--> 属性
//开始对elementsAnnotatedWith进行分类:我们可能很多activity中都定义有 BindView注解,用activity作为key,List作为注解列表
Map<String, List<VariableElement>> map = new HashMap<>();
for (Element element : elementsAnnotatedWith) {
VariableElement variableElement = (VariableElement) element;
//例如MainActivity
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
Class aClass = variableElement.getEnclosingElement().getClass();
List<VariableElement> variableElements = map.get(activityName);
if (variableElements == null) {
variableElements = new ArrayList<>();
map.put(activityName, variableElements); //Activity跟注解列表是一对多的关系
}
variableElements.add(variableElement);
}
//开始遍历map, 生成每个activity对应的模板代码
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);
//获取Activity所在包名
TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
try {
//文件名:MainActivity_ViewBinding
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
writer = sourceFile.openWriter();
// package com.example.annotation;
writer.write("package " + packageName + ";\n");
// import com.example.annotation.IBinder;
writer.write("import " + packageName + ".IBinder;\n");
// public class MainActivity_ViewBinding implements IBinder<com.example.annotation.MainActivity> {
writer.write("public class " + activityName + "_ViewBinding implements IBinder<" +
packageName + "." + activityName + ">{\n");
// @Overrid
// public void bind(com.example.annotation.MainActivity target) {
writer.write(" @Override\n" +
" public void bind(" + packageName + "." + activityName + " target){");
// target.mTvText = (android.widget.TextView) target.findViewById(2131231186);
for (VariableElement variableElement : variableElements) { //这里可能有多个注解的View控件,因此需要循环遍历
//得到名字
String variableName = variableElement.getSimpleName().toString();
//得到ID
int id = variableElement.getAnnotation(BindView.class).value();
//得到类型
TypeMirror typeMirror = variableElement.asType();
writer.write("target." + variableName + "=(" + typeMirror + ")target.findViewById(" + id + ");\n");
}
writer.write("\n}}");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
return false;
}
}
创建完成处理器后,执行build方法,会生成模板代码:
3. 创建辅助类,完成一键绑定
转到App模块,App的build.gradle中做如下依赖:
implementation project(path: ':annotation') //注解
annotationProcessor project(path: ':annotation_compiler') //注解处理器
创建MyButterknife
辅助类,其中IBinder
是我们第2步骤中定义的接口,所有模板类都会继承此接口
public class MyButterknife {
public static void bind(Activity activity) {
String name = activity.getClass().getName() + "_ViewBinding";
try {
Class<?> aClass = Class.forName(name);
IBinder iBinder = (IBinder) aClass.newInstance(); //通过反射,生成模板类的实例
iBinder.bind(activity); //调用这个方法,会去执行对应模板类的bind方法,实现findViewById的功能
} catch (Exception e) {
e.printStackTrace();
}
}
}
在MainActivity中实现代码如下:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tvText)
TextView mTvText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyButterknife.bind(this); //一键实现findViewById功能,
mTvText.setText("Test");
}
}
以上代码已上传 GitHub