《Android 高级进阶》读书笔记
Android 开发中在很多地方都用到了注解,注解在Android中的作用不可谓不大。Android Support Library甚至专门推出了一个支持库Support Annotation,之前的一篇文章Android进阶Support Annotation Library 使用详解 ,基本把注解说的差不多了,这次咱们整体的说下注解。那什么是注解呢?
注解的定义
注解是Java语言的特性之一,它是在源代码中插入的标签,这些标签在后面的编译或者运行过程中起到某种作用。每个注解都必须通过注解接口@interface进行声明,接口的方法对应着注解的元素。
@interface声明会创建一个实际的Java接口,与其他任何接口一样,注解也将会编译成.class文件。注解接口中的元素声明实际上是方法声明,注解接口中的方法没有参数,没有throws语句,也不能使用泛型。
标准注解
Java API中默认定义的注解我们称之为标准注解。它们定义在java.lang、java.lang.annotation和javax.annotation包中。按照使用场景不同,可以分为如下三类。
1. 编译相关注解
编译相关的注解是给编译器使用的,有以下几种。
- @Override:编译器会检查被注解的方法是否真的重载了一个来自父类的方法、如果没有,编译器将会给出错误提示。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
- @Deprecated:可以用来修饰任何不再鼓励使用或已被弃用的属性、方法等。
/**
* @deprecated use {@link #setBackground(Drawable)} instead
*/
@Deprecated
public void setBackgroundDrawable(Drawable background) {
computeOpaqueFlags();
...
}
- SuppressWarnings:可用于除了包之外的其他声明项中,用来抑制某种类型的警告。
//源码中的注释说的非常详细
/*
* <p>As a matter of style, programmers should always use this annotation
* on the most deeply nested element where it is effective. If you want to
* suppress a warning in a particular method, you should annotate that
* method rather than its class.
* /
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
- @SafeVarargs:用于方法和构造函数,用来断言不定长参数可以安全使用。
- @Generated:一般是给代码生成工具使用,用来表示这段代码不是开发者手动编写的,而是工具生成的。被@Generated修饰的代码一般不建议手动修改它。
- @FunctionalInterface:用来修饰接口,表示对应的接口是带单个方法的函数式接口。
资源相关注解
资源相关的注解有四个,一般用在JavaEE领域,Android开发中应该不会用到,罗列一下:
- @PostConstruct:用在控制对象生命周期的环境中,例如Web容器和应用服务器,表示在构造函数之后应该立即调用被该注解修饰的方法。
- @PreDestory:表示在删除一个被注入的对象之前应该立即调用被该注解修饰的方法。
- @Resource:用于Web容器的资源注入,表示单个资源。
- @Resources:用于Web容器的资源注入,表示一个资源数组。
元注解
元注解,就是用来定义和实现注解的注解,共分为5种:
- @Target:这个注解的取值是一个ElementType类型的数组,用来指定注解所适用的对象范围,总共有10种不同的类型,根据定义的注解进行灵活的组合。
元素类型 | 适用于 |
---|---|
ANNOTATION_TYPE | 注解类型声明 |
CONSTRUCTOR | 构造函数 |
FIELD | 实例变量 |
LOCAL_VARIABLE | 局部变量 |
METHOD | 方法 |
PACKAGE | 包 |
PARAMETER | 方法参数或者构造函数的参数 |
TYPE | 类(包含enmu)和接口(包含注解类型) |
TYPE_PARAMETER | 类型参数 |
TYPE_USER | 类型的用图 |
同时支持多个注解类型的注解定义如下:
@Target({ElementType.TYPE,ElementType.PACKAGE})
public @interface CrashReport
如果一个注解的定义没有使用@Target修饰,那么它可以用在除了TYPE_USE和TYPE_PARAMEYER之外的其他类型中。
@Retention:用来指明注解的访问范围,也就是在什么级别保留注解,有如下三种:
- 源码级注解:在定义注解接口时,使用@Retention(RetentionPolicy.SOURCE)修饰的注解,该类型的注解信息只会保留在.java源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的.class文件中。
- 编译时注解:在定义注解接口时,使用@Retention(RetentionPolicy.CLASS)修饰的注解,该注解的注册信息会保留在.java源码里和.class文件里,在执行的时候,会被Java虚拟机丢弃,不会加载到虚拟机种。
- 运行时注解:在定义注解接口时,使用@Retention(RetentionPolicy.RUNTIME)修饰的注解,Java虚拟机在运行期也保留注解信息,可以通过反射机制读取注解的信息(.java源码,.class文件和执行的时候都有注解的信息)
未指定类型时,默认是CLASS类型。
- @Documented:表示被修饰的注解应该被包含在被注解项的文档中(例如用JavaDoc生成的文档)。
- @Inherited:表示该注解可以被子类继承的。
- @Repeatable:表示这个注解可以在同一个项上面应用多次,不过这个注解是在Java8中才引入的,前面四个元注解都是在Java5中就已经引入的。
运行时注解
要定义运行时注解,只需要在声明注解时指定@Retention(RetentionPolicy.RUNTIME)即可,运行时注解一般和反射机制配合使用,相比编译时注解性能比较低,但灵活性好,实现起来比较简答。
编译时注解
编译时注解能够自动处理Java源文件并生成更多的源码、配置文件、脚本或其他可能想要生成的东西。这些操作时通过注解处理器完成的。Java编译器集成了注解处理、通过在编译期间调用javac -processor命令可以调起注解处理器,它能够允许我们实现编译时注解的功能,从而提高函数库的性能。
定义注解处理器
在编译期间,编译器会定位到Java源文件中的注解,注解处理器会对其感兴趣的注解进行处理,需要注意的是,一个注解处理器只能产生新的源文件,它不能修改一个已经存在的源文件。注解处理器一般通过继承AbstractProcessor类并实现process方法,同时需要指定这个处理器能够处理的注解类型以及支持的Java版本,
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
@Override
public Set<String> getSupportedAnnotationTypes() { }
@Override
public SourceVersion getSupportedSourceVersion() { }
}
- init(ProcessingEnvironment env):每个注解处理器都必须有个空的构造方法。不过,有一个特殊的init方法,它会被注解处理器工具传入一个ProcessingEnvironment作为参数来调用。ProcessingEnvironment提供了一些有用的工具类,如Elements,Types和Filter。我们后面会用到它们。
- process(Set? extends TypeElement> annotations, RoundEnvironment env):这个方法可以看做每个处理器的main方法。你要在这里写下你的扫描,判断和处理注解的代码,并生成java文件。通过传入的RoundEnvironment参数,你可以查询被某个特定注解注解的元素,我们稍后会看到。
- getSupportedAnnotationTypes( ):这里你需要说明这个处理器需要针对哪些注解来注册。注意返回类型是一个字符串的Set集合,包含了你要用这个处理器处理的注解类型的全名。
- getSupportedSourceVersion( ):用于指定你使用的java版本。通常返回SourceVersion.latestSupported( )即可,当然也可以明确指定只支持某个Java的版本
从Java7开始,我们也可以使用注解来代替上面的getSupportedAnnotationTypes( )和getSupportedSourceVersion( )方法,语句如下:
@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
//该注解处理器支持的所有注解全名字符串类型的集合
})
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}
注册注解处理器
注册处理器定义好之后,为了让javac -processor能够对其进行处理,我们需要将注解处利器打包到一个jarwenjianzhong ,tongshi ,xuyao zia jar文件中增加一个名为javax.annotation.processing.Processor的文件来指明jar文件中有哪些注解处理器,这个文件最终的路径位于jar文件根目录中的META-INF/services目录中,所以你的.jar文件内容看起来会像这个样子:
MyProcessor.jar
- com
- example
- MyProcessor.class
- META-INF
- services
- javax.annotation.processing.Processor
javax.annotation.processing.Processor文件的内容是注解处理器全路径名,如果存在多个注解处理器,以换行符进行分隔,
com.example.MyProcessor
com.foo.OtherProcessor
net.blabla.SpecialProcessor
这样, 一个注解处理器的框架就好了, 完成代码后编译成jar文件, 然后像开始介绍的那样添加依赖就好了.
手动执行上面的注册过程是很繁琐的,因此Google开源了一个名为AutoService的函数库来解放开发者的双手,引入这个函数库之后,我们只需要在定义自己的Processor时使用@AutoService注解标记即可完成上面的注册步骤,如下:
@AutoServices(Processor.class)
@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
//该注解处理器支持的所有注解全名字符串类型的集合
})
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}
android-apt插件
注解处理器所在的jar文件只有在编译期间起作用,到应用运行时不会用到,因此在build.gradle中引入依赖时应该以provided方式而不是compile方式引入,语句如下:
dependencies{
provided '***'
compile '***'
}
当然,我们还可以使用android-apt插件的方式。android-apt是在Android Studio中使用注解处理器的一个辅助插件,它的主要作用如下:
- 只在编译期间引入注解处理器所在的函数库作为依赖,不会打包到最终生成的APK中。
- 为注解处理器生成的源代码设置好正确的路径,以便Android Studio能够正常找到。
android-apt的使用很简单,首先在使用到注解处理器函数库的模块的build.gradle文件中引入apt插件
buildscript {
repositories {
mavenCentral()
}
dependencies {
...
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
接着以apt的方式引入注解处理器函数库作为依赖
dependencies {
apt '***'
compile '***'
}
到这里注解都已经说的差不多了,有什么问题欢迎留言给我。