上一篇文章我们学习了怎样自定义运行时的注解功能,并且简单的实现了类似ButterKnife的功能。但是通常情况下,我们使用的ButterKnife或者Eventbus、AndroidAnnotations,他们都使用的是编译时的注解功能。
编译时注解跟运行时注解到底区别在什么地方?其实说大也不大,主要是考虑到性能上面的问题。运行时注解主要是完全依赖于反射,反射的效率比原生的慢,所以在内存比较少,CPU比较烂的机器上会有一些卡顿现象出现。而编译时注解完全不会有这个问题,因为它在我们编译过程(java->class)中,通过一些注解标示,去动态生成一些类或者文件,所以跟我们的APK运行完全没有任何关系,自然就不存在性能上的问题。所以一般比较著名的开源项目如果采用注解功能,通常采用编译时注解
本文最终demo已经上传到github上,欢迎大家star,follow
唠叨了这么多,我们就学习一下如何自己去实现一个编译时注解功能,因为我想大家日常用这类框架太多了,没必要去教你如何去用了吧
目标
- 项目1:在编译过程中打印相关自定义信息
- 项目2 : 实现类似AndroidAnnotations中@EActivity的功能
项目搭建
首先创建一个as的工程,这个没啥好说的。然后我们创建一个注解库。这个库的学问大了,我们不能使用一般的Android Library,而是得使用Java Library,因为我们要使用到javase里面的东西,android.jar里面缺少相应部分。当然你无需担心这些东西在手机里面使用,因为我们仅仅是在电脑上编译需要用到这些
这边还有一个地方要注意,我们要在library的build.gradle里面配置一下java的使用版本,同RetroLambda差不多,这边我是在一系列的报错中没办法,就去看看ButterKnife是怎么写的,然后放到我这边尝试,完美解决
apply plugin: 'java'
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
这边不要用1.8,因为只有android N支持java8,如果你写1.8之后,强制要你使用buildToolsVersion为24.0.0
这样,项目就搭建好了,下面开始写代码
打印功能Library的实现
1 注解类的实现
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface PrintInject {
int[] value();
}
看过上面一篇文章的朋友应该一目了然
2 实现AbstractProcessor,重写其中的process方法
AbstractProcessor是何方神圣?apt(Annotation Processing Tool)在编译时自动查找所有继承自AbstractProcessor的类,然后调用他们的process方法去处理,这样就拥有了在编译过程中执行代码的能力
@SupportedAnnotationTypes("com.example.PrintInject")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class PrintInjectProcessor extends AbstractProcessor {
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
Messager messager = processingEnv.getMessager();
for (TypeElement te : annotations) {
for (Element e : roundEnv.getElementsAnnotatedWith(te)) {
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.toString());
}
}
return true;
}
}
这边我需要详细的解释一下,因为后期要使用类似的一些对象来完成更复杂的功能
SupportedAnnotationTypes: 用来表明注释处理器支持哪些注释类型的注释,这里就是指我们刚才定义的PrintInject这个类
SupportedSourceVersion: 用来表明注释处理器支持到的JAVA版本
processingEnv: 注释处理工具框架将提供一个工作环境processingEnv,使其可以使用该框架提供的功能来编写新文件、报告错误消息并查找其他实用工具。
TypeElement: 我们所定义的全部不同注解类型,好比这里应该返回PrintInject.class
getElementsAnnotatedWith: 返回使用相应注解类型注解的全部元素。你写了几个这种类型的注解,Set的长度就会有多长
printMessage: 使用messager去直接打印不同类型的不同信息。类似于Log,这边是Kind,有NOTE,ERRPR,WARNING之类,我们一般在Android Studio控制台里面看到的各种信息,就有一些是他们打印出来的
现在我们差不多能够理解这几行代码的意思了:打印相关被注解元素的信息
3 服务注册文件(META-INF/services/javax.annotation.processing.Processor)
为了便于编译器注释工具执行能够运行PrintInjectProcessor,必须要用一个服务文件来注册它。文件里面就是写着Processor的位置
com.example.PrintInjectProcessor
OK现在编译一下,就能得到jar包
Android工程使用
跟一般的jar一样,直接拷贝到libs文件夹下面去即可
@PrintInject({1})
public class MainActivity extends AppCompatActivity {
@PrintInject({3})
TextView textview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@PrintInject({2})
public void click() {
Toast.makeText(this, "HHH", Toast.LENGTH_SHORT).show();
}
}
我们分别在Type、Field、Method上面加了打印的注解,这个时候我们就可以开始编译了,gradle build走你
注: Printing: com.rg.annotationdemo.MainActivity
注: Printing: textview
注: Printing: click()
看到了吧,被注解的元素信息就这样打印出来了
作为第一个项目,咱们就简单的先了解一下这个流程,让它在编译中执行一些代码,下面一篇我们就要真刀真枪的干了。我将结合鸿洋的demo进行说明。这个demo能不能运行我不知道,因为他的项目是Eclipse上的,Android Studio上面的实现步骤与Eclipse还是有区别的,最重要的是demo没有什么注释,没有什么注释,没有什么注释吗,所以正好适合咱们练手