-
什么是注解?
-
Annontation 是Java5开始引入的新特征,中文名称叫 注解 。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
-
注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义。 Java 注解(Annotation)又称 Java 标注,是 JDK1.5 引入的一种注释机制。是元数据的一种形式,提供有关程序但不属于程序本身的数据。注解对他们注解的代码的操作没有直接影响
-
-
元注解
-
@Target 所修饰的对象范围:
-
CONSTRUCTOR:用于描述构造器
-
FIELD:用于描述域即类成员变量
-
METHOD:用于描述方法
-
PARAMETER:用于描述参数
-
TYPE:用于描述类、接口(包括注解类型) 或enum声明
-
-
@Retention 所修饰对象的保留级别:
-
SOURCE:注解仅保留在源码级别,并被编译器忽略
-
CLASS:在编译器时由编译器保留,会被JVM忽略
-
RUNTIME:由JVM保留,代码运行时会用到
-
-
用的最多是这俩元注解,其他的本文暂不做扩展,日后用到再补齐
Retention这块面试常会问道,试着写写
注解的应用场景
RetentionPolicy.SOURCE
运行在编译阶段
此模块主要用于语法检查和APT技术
先来个语法检查的栗子:
注解类
输入只能是
SUNNY,MONDAY
作用范围:类的成员变量、参数
保留级别:源码
需求:语法检查
@IntDef({SUNNY,MONDAY})
@Target({ElementType.FIELD,ElementType.PARAMETER})
@Retention(RetentionPolicy.SOURCE)
public @interface Wek {
}
实现类
public class MainActivity extends AppCompatActivity {
public static final int SUNNY = 1;
public static final int MONDAY = 2;
@Wek
private int current;
@InjectView(R.id.text)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 正确的填入方式
setCurrentDay(SUNNY);
}
private void setCurrentDay(@Wek int day){
current = day;
}
}
APT栗子再来一个:
apt全称 Android annotation tools ,在Javac执行前生成辅助代码文件,比如
ButterKnife框架,相比于
Android Annotations框架更轻量级,仅在编译期间多花点时间
项目实现
-
创建 Java library Module,命名 apt-annotation
-
这个module用于盛放注解类
-
-
创建 Java library Module,命名为 apt-processor,依赖apt-annotation
-
处理注解,在编译器生成辅助类
-
-
创建 Android library Module,依赖 apt-annotation
-
调用生成的辅助类,反射实现view绑定
-
-
app module 依赖上面三个
一步一步实现它
1.apt-annotation
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
// 获取填入的id
int value();
}
2.apt-processor
加入依赖
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':apt-annotation')
}
ClassCreator.java辅助类
/**
* 辅助类
*/
public class ClassCreator {
private String className;
private String packageName;
private TypeElement mTypeElement;
private Map<Integer, VariableElement> mVariableElementMap;
/**
* 获取包名、类名
* @param classElement
* @param packageName
*/
public ClassCreator(TypeElement classElement,String packageName) {
mVariableElementMap = new HashMap<>();
mTypeElement = classElement;
this.packageName = packageName;
className = classElement.getSimpleName().toString() + "_BindView";
}
/**
* id、view成对放入
*/
public void setElement(int id,VariableElement element){
mVariableElementMap.put(id, element);
}
/**
* 生成 java 代码
* @return
*/
public String generateJavaCode(){
StringBuilder builder = new StringBuilder();
builder.append("package " + packageName + ";\n\n");
builder.append("import com.miss.apt_library.*;\n\n");
builder.append("public class " + className);
builder.append("{\n");
generateMethods(builder);
builder.append("\n}");
return builder.toString();
}
/**
* 把要 find id 写进去
* @param builder
*/
private void generateMethods(StringBuilder builder) {
builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host){ \n");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
builder.append("host." + name + " = ");
builder.append("((android.app.Activity)host).findViewById(" + id + ");\n");
}
builder.append("};");
}
public String getClassFullName() {
return packageName + "." + className;
}
public TypeElement getTypeElement() {
return mTypeElement;
}
}
BindViewProcessor.java 获取注解中的id,生成注解类
/**
* 获取注解中的id,生成辅助类
* Created by Vola on 2020/8/2.
*/
@SupportedAnnotationTypes("com.miss.apt_annotation.BindView")
public class BindViewProcessor extends AbstractProcessor {
private Messager messager;
private Elements elementUtils;
private Map<String, ClassCreator> mProxyMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnv.getMessager();
elementUtils = processingEnv.getElementUtils();
}
/**
* 用来指定你使用的Java版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 这里必须指定,这个注解处理器是注册给哪个注解的。
* 注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。
* 换句话说,在这里定义你的注解处理器注册到哪些注解上。
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
/**
* 这相当于每个处理器的主函数main()。
* 在这里写扫描、评估和处理注解的代码,以及生成Java文件。
* @param set
* @param roundEnvironment 输入参数RoundEnviroment,可以让查询出包含特定注解的被注解元素。
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
messager.printMessage(Diagnostic.Kind.NOTE, "process---->");
mProxyMap.clear();
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
//因为 BindView 的作用对象是 FIELD,因此 element 可以直接转化为 VariableElement
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
String classFullName = classElement.getQualifiedName().toString();
ClassCreator classCreator = mProxyMap.get(classFullName);
if (classCreator == null) {
String packageName = elementUtils.getPackageOf(classElement).getQualifiedName().toString();
classCreator = new ClassCreator(classElement, packageName);
mProxyMap.put(classFullName, classCreator);
}
BindView bindView = variableElement.getAnnotation(BindView.class);
int id = bindView.value();
classCreator.setElement(id, variableElement);
}
//通过遍历mProxyMap,创建java文件
for (String key : mProxyMap.keySet()) {
ClassCreator classCreator = mProxyMap.get(key);
try {
messager.printMessage(Diagnostic.Kind.NOTE, " --> " + classCreator.generateJavaCode());
JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(classCreator.getClassFullName(), classCreator.getTypeElement());
Writer writer = fileObject.openWriter();
writer.write(classCreator.generateJavaCode());
// 清空缓冲区数据
writer.flush();
writer.close();
} catch (IOException e) {
messager.printMessage(Diagnostic.Kind.NOTE, " --> create " + classCreator.getClassFullName() + "error");
}
}
return true;
}
}
注册processor
src/
resources/META-INF/services/javax.annotation.processing.Processor(下划线部分是写死的)
com.miss.apt_processor.BindViewProcessor
3.apt-library
加入依赖
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation project(':apt-annotation')
}
BindViewTool.java
public class BindViewTool {
/**
* 实现 view 绑定
*/
public static void bind(Activity activity) {
Class clazz = activity.getClass();
try {
Log.e("aa", "----" + clazz.getName());
Class bindClass = Class.forName(clazz.getName() + "_BindView");
Method method = bindClass.getMethod("bind", activity.getClass());
method.invoke(bindClass.newInstance(), activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
4.app
依赖
dependencies {
implementation project(':apt-library')
implementation project(':apt-annotation')
annotationProcessor project(':apt-processor')
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
@BindView(R.id.text)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BindViewTool.bind(this);
textView.setText("Success");
}
}