Annotation注解在Android的开发中的使用越来越普遍,例如EventBus、ButterKnife、Dagger2等,记一次使用插件annotationProcessor实现下载监听注解框架,为什么不用android-apt呢,我不会告诉你android-apt不再维护了。
Javadoc代码注解-代码文档:传送门
注解简介
注解
Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
注解是一种元数据, 可以添加到java代码中. 类、方法、变量、参数、包都可以被注解,注解对注解的代码没有直接影响。之所以产生作用, 是对其解析后做了相应的处理. 注解仅仅只是个标记。
定义注解用的关键字是@interface
注解的用处
- 生成文档,编译进行检查。这是最常见的,即Javadoc代码注解
- 跟踪代码依赖性,实现替代配置文件功能。如EventBus、ButterKnife、Dagger2依赖注入等,本篇记录一次编译注解使用过程。
元注解
java.lang.annotation提供了四种元注解,专门注解其他的注解:
@Documented –注解是否将包含在Javadoc文档中
@Retention –什么时候使用该注解,有三种选择,默认为CLASS
- RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
- RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
- RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。
@Target –表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括
- ElementType.CONSTRUCTOR:用于描述构造器
- ElementType.FIELD:成员变量、对象、属性(包括enum实例)
- ElementType.LOCAL_VARIABLE:用于描述局部变量
- ElementType.METHOD:用于描述方法
- ElementType.PACKAGE:用于描述包
- ElementType.PARAMETER:用于描述参数
- ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Inherited – 是否允许子类继承该注解,默认为false
Android中使用编译时注解,自动生成代码
这里只讲解编译注解,其他注解待续~~~
- DownloadAnnotations:Java工程,申明使用的注解
- DownloadCompiler:Java工程,用于编译期间自动生成代码
- DownloaderLibrary:下载依赖库
- Demo:测试示例
创建自定义注解
创建Java工程:DownloadAnnotations
声明一个Download注解,声明周期为Class,作用域为方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Download
{
/**
* 下载开始,获取文件大小
* @see com.excellence.downloader.FileDownloader.DownloadTask#getFileSize()
*
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
@interface onPreExecute
{
String[] value() default { NO_URL };
}
}DownloadAnnotations的build.gradle配置
1
2
3
4
5
6
7
8
9
10
11
12apply plugin: 'java'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
解析编译时注解
创建Java工程:DownloadCompiler
解析编译时注解需要继承AbstractProcessor,并且使用注解@AutoService(Processor.class)
,该注解在编译时自动执行被注解的类,即自动执行该类的Java程序,用于创建类文件。
DownloadProcessor事件注解扫描器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58(Processor.class)
public class DownloadProcessor extends AbstractProcessor
{
private ElementHandler mElementHandler = null;
/**
* 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的{@link #init}方法,它会被注解处理工具调用,并输入{@link ProcessingEnvironment}参数。{@link ProcessingEnvironment}提供很多有用的工具类{@link Elements}、{@link Types}、{@link Filer}。
*
* @param processingEnvironment
*/
public synchronized void init(ProcessingEnvironment processingEnvironment)
{
super.init(processingEnvironment);
mElementHandler = new ElementHandler(processingEnvironment.getFiler(), processingEnvironment.getElementUtils());
}
/**
* 必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,在这里定义你的注解处理器注册到哪些注解上。
*
* @return
*/
public Set<String> getSupportedAnnotationTypes()
{
Set<String> annotations = new LinkedHashSet<>();
annotations.add(Download.onPreExecute.class.getCanonicalName());
return annotations;
}
/**
* 用来指定你使用的Java版本。
*
* @return
*/
public SourceVersion getSupportedSourceVersion()
{
return SourceVersion.latestSupported();
}
/**
* 这相当于每个处理器的主函数main()。 在这里写扫描、评估和处理注解的代码,以及生成Java文件。输入参数{@link RoundEnvironment},可以让查询出包含特定注解的被注解元素。
* 该方法返回ture表示该注解已经被处理, 后续不会再有其他处理器处理; 返回false表示仍可被其他处理器处理
*
* @param set
* @param roundEnv
* @return
*/
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)
{
mElementHandler.clean();
mElementHandler.handleDownload(roundEnv);
mElementHandler.createProxyFile();
return true;
}
}ElementHandler元素处理(代码过长,请查看源码)
编译时自动创建事件代理的类文件的管理类,用于管理有注册代理的类,生成路径:
Demo/build/generated/source/apt/debug/com/excellence/downloader/ProxyClassCounter
编译时自动创建事件代理的类文件,用于发送监听接口,生成类文件的路径:
Demo/build/generated/source/apt/debug/com/zv/downloader/downloader/SingleThreadActivity$$DownloadListenerProxy
DownloadCompiler的build.gradle配置
com.google.auto.service:auto-service:1.0-rc2
谷歌提供的Java 生成源代码库com.squareup:javapoet:1.9.0
提供了各种 API 让你用各种姿势去生成 Java 代码文件,javapoet传送门1
2
3
4
5
6
7
8
9
10
11
12
13
14
15apply plugin: 'java'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.9.0'
compile project(':DownloadAnnotations')
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
到此注解生成代码过程结束
Downloader依赖库处理注解
根据DownloadCompile里的ElementHandler类创建注解接口ISchedulerListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14public interface ISchedulerListener<TASK>
{
void onPreExecute(TASK task);
void onProgressChange(TASK task);
void onProgressSpeedChange(TASK task);
void onCancel(TASK task);
void onError(TASK task);
void onSuccess(TASK task);
}DownloadScheduler事件调度器,注册、解绑、监听,用于处理任务状态的调度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35/**
* 初始化代理参数,获取注解类集合
*/
private void initDownloadCounter()
{
try
{
Class clazz = Class.forName("com.excellence.downloader.ProxyClassCounter");
Method download = clazz.getMethod("getDownloadCounter");
Object object = clazz.newInstance();
Object downloadCounter = download.invoke(object);
if (downloadCounter != null)
mDownloadCounter = unmodifiableSet((Set<String>) downloadCounter);
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
catch (NoSuchMethodException e)
{
e.printStackTrace();
}
catch (InstantiationException e)
{
e.printStackTrace();
}
catch (IllegalAccessException e)
{
e.printStackTrace();
}
catch (InvocationTargetException e)
{
e.printStackTrace();
}
}
Demo使用注解监听
使用方式
1 | // 注册 |