Android中应该了解的注解知识(Android进阶之光笔记)
注解
本文讲解一些Android中用到的基本注解只是及ButterKnife和Dagger2原理
注解分类
注解分为标准注解和元注解
标准注解
- @Override:对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出警告.
- @Deprecated:对不鼓励使用或者已经过时的方法进行注解,当编程人员使用这些方法时,将会在编译时显示提示信息
- @SupressWarnings:选择性的取消特定代码中的警告
- @SafeVarargs:JDK7新增,用来声明使用了可变长度参数的方法,其在与泛型类一起使用时不会出现类型安全问题
元注解
元注解是用来注解其他的注解,从而创建新的注解
- @Target:注解所修饰的对象范围
- @Inherited:表示注解可以被继承
- @Documented:表示这个注解应该被JavaDoc工具记录
- @Retention:用来声明注解的保留策略
- @Repeatable:JDK8新增,允许同一注解在同一声明类型(类,属性或方法)上多次使用
@Target注解取值是一个ElementType类型的数组,有以下几种取值
- ElementType.TYPE:能修饰类,接口和枚举类型
- ElementType.FIELD:能修饰成员变量
- ElementType.METHOD:能修饰方法
- ElementType.PARAMETER:能修饰参数
- ElementType.CONSTRUCTOR:能够修饰构造方法
- ElementType.LOCAL_VARIABLE:能修饰局部变量
- ElementType.ANNOTATION_TYPE:能修饰注解
- ElementType.PACKAGE:能修饰包
- ElementType.TYPE_PARAMETER:类型参数声明
- ElementType.TYPE_USE:使用类型
@Retention注解有三种类型,表示不同级别的保留策略
- RetentionPolicy.SOURCE:源码级注解,注解信息只会保留在.java源码中,源码在编译后,注解信息被丢弃,不会保留在.class中
- RetentionPolicy.CLASS:编译时注解,注解信息之后保留在.java源码以及.class中,当运行Java程序时,JVM会丢弃注解信息,不回保留在JVM中
- RetentionPolicy.RUNTIME:运行时注解,当运行Java程序时,JVM也会保留该注解信息,可以通过反射获取该注解信息
定义注解
基本定义
定义新的注解类型使用@interface关键字,和定义接口很像
public @interface Swordsman{
}
使用注解
@Swordsman
public class AnnotationTest{
}
定义成员变量
注解只有成员变量,没有方法,注解的成员变量在注解定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型
public @interface Swordsman {
String name();
int age();
}
使用该注解时就应该为该注解的成员变量指定值
@Swordsman(name = "张三",age = 23)
public class AnnotationTest {
}
定义成员变量时,使用default关键字为其指定默认值(使用默认值时就不需要传入参数了)
public @interface Swordsman {
String name() default "张三丰";
int age() default 99;
}
定义运行时注解
用@Retention来设定注解的保留策略,三种策略的生命周期长度为SOURCE《CLASS《RUNTIME,生命周期短的能起作用的地方,生命周期长的也一定能起作用.
- 如果要在运行时去动态获取注解信息,只能用RetentionPolicy.RUNTIME;
- 如果要在编译时进行一些预处理,比如生成一些辅助代码,就使用RetentionPolicy.CLASS
- 如果只要做一些检查性的操作,如@Override和@SuppressWarnings,则可选用RetentionPolicy.SOURCE
@Retention(RetentionPolicy.RUNTIME)
public @interface Swordsman {
String name() default "张三丰";
int age() default 99;
}
注解器处理
如果没有处理注解的工具,注解也不会有太大的作用,对于不同的注解有不同的注解处理器,注解器的处理标准
- 针对运行时注解采用反射机制处理
- 针对编译时注解采用AbstractProcessor处理
编写运行时注解处理器
运行时注解需要用到反射机制
@Documented
@Target(ElementType.METHOD)//定义方法
@Retention(RetentionPolicy.RUNTIME)
public @interface GET{
String value() default "";
}
上面代码是Retrofit中定义的@GET注解
@GET(value = "http://baidu.com")
public String getIpMsg() {
return "";
}
写一个简单的注解处理器
public static void main(String [] args){
Method[] methods = MainActivity.AnnotationTest.class.getDeclaredMethods();
for (Method method : methods) {
MainActivity.AnnotationTest.GET get = method.getAnnotation(MainActivity.AnnotationTest.GET.class);
System.out.println(get.value());
}
}
getDeclaredMethods和getAnnotation俩个反射方法都属于AnnotatedElement接口,Class,Method和Filed等类都实现了该接口,调用getAnnotation方法返回指定类型的注解对象,也就是GET,调用GET的value方法返回从GET对象中提取的元素的值
编译时注解处理器
定义注解
创建Java Library来专门存放注解,Library名为annotations
这个注解类似于ButterKnife的@BindView
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default -1;
}
创建Java Library存放注解处理器,Library命名为processor,配置processor的build.gradle依赖annotations
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile project(':annotations')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
编写注解处理器ClassProcessor
//Java7以后,使用下面俩个注解代替对应的方法,但考虑兼容问题,一般还是实现方法
//@SupportedSourceVersion(SourceVersion.RELEASE_8)
//@SupportedAnnotationTypes("com.yangdxg.annotation.cls.BindView")
public class ClassProcessor extends AbstractProcessor {
/**
* 被注解处理工具调用,输入processingEnvironment参数
* processingEnvironment提供很多有用的工具类,如Elements,Types,Filer和Messager等
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
/**
* 相当于每个处理器的祝函数main(),这里写扫描,评估和处理注解的代码以及生成java文件,
* 出入参数roundEnviroment,可以查询出包含特定注解的被注解元素
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
/**
* 必须指定的方法,指定这个注解处理器是注册给那个注解的,返回一个字符串的集合,包含本处理器想要处理的注解类型的合法全称
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
LinkedHashSet<String> annotations = new LinkedHashSet<>();
annotations.add(BindView.class.getCanonicalName());
return annotations;
}
/**
* 指定使用的Java版本
* 一般返回 SourceVersion.latestSupported()
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
实现process方法
@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方法来打印出注解修饰的成员变量的名称
messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:" + element.toString());
}
}
return true;
}
注册注解处理器
为了使用注解处理器,需要用一个服务文件来注册,创建这个服务文件
- 在processor库的main目录下新建resources资源文件夹
- 在resources中建立META-INF/services目录文件夹
- 在META-INF/services中创建javax.annotation.processing.Processor文件,这个文件的内容是注解处理器的名称,这里文件内容是com.yangdxg.processor.ClassProcessor
可以使用AutoService帮助完成上面步骤
- 添加依赖auto-sercvice
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile project(':annotations')
compile 'com.google.auto.service.auto-service:1.0-rc2'
}
- 在注解处理器中添加@AutoService(Processor.class)
@AutoService(Processor.class)
public class ClassProcessor extends AbstractProcessor {
- 在app中添加对注解器的依赖
compile project(':annotations')
compile project(':processor')
- 在Activity中使用注解
@BindView(value = R.id.tv_text)
TextView mTextView;
- 先对工程clean再Make Project,在Gradle Console中就打印出了注解方法
注: printMessage:mTextView
使用android-apt插件
应用了processor库,但注解处理器只在编译处理期间需要用到,编译处理完后就没有实际作用了,而主工程添加了这个库会引入很多不必要的文件,为了解决这个问题引入插件android-apt,它的作用是
- 仅仅在编译时期去依赖注解处理器所在的函数库并进行工作,但不会打包到APK中
- 为注解处理器生成的代码设置好路径,以便Android Studio能够找到它
- 在app的build.gradle中以apt的方式引入注解处理器processor
dependencies {
annotationProcessor ':processor'
依赖注入的原理
控制反转
为了解决对象之间耦合度过高的问题,提出了IoC理论,用来实现对象之间的解耦,即控制反转,借助第三方实现具有依赖关系的对象之间的解耦
依赖注入
控制反转是获得依赖对象的过程被反转了,控制反转之后,获得依赖对象的过程由自身管理变为由IoC容器主动注入
依赖注入的实现方式
构造方法注入
public class Car{
private Engine mEngine;
public Car(Engine engine){
this.mEngine=engine;
}
}
Setter方法注入
public class Car{
private Engine mEngine;
public void set(Engine engine){
this.mEngine=engine;
}
}
接口注入
public interface ICar{
public void setEngine(Engine engine);
}
Car类实现ICar
public class Car implement ICar{
private Engine mEngine;
public void setEngine(Engine engine){
this.mEngine=engine;
}
}
通过以上三种方式,Car和Engine解耦合了,Car不关心Engine的实现,即使Engine的类型变换了,Car也不需要做修改