Android注解笔记
简介
关于注解的文章网上也很多了,文章也很不错,本文总结记录在开发注解时所踩得坑,希望对你在开发中有所帮助。
不太清楚注解是何物?以及如何开发使用注解可以参考一下链接:
秒懂,Java 注解 (Annotation)你可以这样学
Android中注解的使用
Android编译时注解
注解概念
注解,可以理解为一种标签!对代码贴上(标签)注解,能使我们快速认识代码,以及辅助代码完成某一特定的功能。
注解
@Retention(CLASS) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
作用对象@Target
它指明我们这个BindView注解可以用在哪些地方,@Target可以取以下值:
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE
}
比如Android中的ButterKnife中@BindView为FILED,可以用在成员属性上
生存范围@Retention
它指明注解存活使用的生命范围,可以取以下值:
public enum RetentionPolicy {
SOURCE, 源代码有效
CLASS, 编译时有效
RUNTIME 运行时有效
}
如@Override,为SOURCE只是在源代码有效,起标识作用,因为方法重写是java的语言特性,而不是Override带来的功能,所以只是标识作用,重写方法时要不要他都无所谓
注解成员
注解内部没有方法,只有成员以及默认default初值,在我们使用注解的时候为这些成员赋值,最后在注解解析器时会用到这些值
注解原理
开发时,我们开发了注解,并完成了作用对象和生存范围,然后再把我们开发的注解,如BindView用到其他代码处,这个时候除了SOURCE类型的注解外,还不能正常工作,因为它还需要一个注解解析器。
一般来说,注解解析器都是运用了反射原理,扫描或者获取java类中的注解,然后反射获取该注解修饰的成员、方法、参数等,然后把注解的赋值,通过反射为这些成员赋值,或者调用其他方法来完成相应的功能
编译时注解
编译时注解只在编译时有效,一般来说,编译时注解是为了生存一些中间辅助类,然后运行时用到。
首先,我们在开发在Module里面开发,然后在主工程app内引入
apt-annotation是我们开发的注解工程(如BindView.java),java-lib
apt-api是开发注解的注入工程(如ButterKnife.java),android-lib
apt-processor则是注解解析器工程,java-lib,这部分通过annotationProcessor引入主工程,源代码将不会封装到apk
- apt-processor需要引入的工程:
implementation project(':apt-annotation')
implementation 'com.squareup:javapoet:1.10.0'
implementation 'com.google.auto.service:auto-service:1.0-rc4'
apt-annotation是定义的注解工程,因为注解解析器需要知道我们要分析哪些注解
javapoet依赖主要是用于编译时执行
auto-service是为了让我们自动执行我们的解析器用的
但是这里我开发完成后并没有执行,最后还是我执行了我的注解解析器在哪里才执行了
在apt-processor工程下创建src/main/resources/META-INF/services/javax.annotation.processing.Processor,在这个文本文件中指明我的解析器,编译就自动生成了我的java文件
- 开发解析器
@AutoService(Processor.class)
//我需要解析哪些注解,可以是多个参数
@SupportedAnnotationTypes("com.my.BindeView")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class XAptProcessor extends AbstractProcessor{
@Override 这个方法就是解析器的核心,执行逻辑再此
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindeView.class);
for (Element ele : elements) {
1. 拿到每个Element的所属类
2. 获取ele的注解以及注解里面的值
3. 类名为key 注解以及ele为value存储到Map中
}
读取完所有注解后
4. 依次取出每个类,以及其注解
5. 为每个类创建方法MethodSpec
6. 为每个类创建辅助类TypeSpec
7. 创建java文件JavaFile
}
}
- 编写注解注入类,在代码中调用此类,利用反射生成我们的辅助类
public class XcInject {
public static void bind(Object host) {
String name = host.getClass().getName();
try {
Class<?> aClass = Class.forName(name + "_ViewInjector");
Method method = aClass.getMethod("inject", host.getClass(), Object.class);
method.invoke(aClass.newInstance(),host,host);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
最后,我们在代码中调用XcInject.bind(this),就是在内存中生成我们的辅助类了,在用反射调用方法即可
运行时注解
运行时注解和上面类似,但是是在运行时JVM中执行,在运行时,通过获取类的class,然后获取注解以及修饰成员,反射进行赋值等
总结
注解是为了提高我们开发效率而产生的,要注意它的使用场景和需求,因为他很多地方都用到了反射原理,如果某些场景优先时间效率,尽量不要使用运行时注解,建议换成编译时注解,使用反射较少,或者用其他代码逻辑替换