如何实现自定义Java编译时注解功能--初步印象

上一篇文章我们学习了怎样自定义运行时的注解功能,并且简单的实现了类似ButterKnife的功能。但是通常情况下,我们使用的ButterKnife或者Eventbus、AndroidAnnotations,他们都使用的是编译时的注解功能。

编译时注解跟运行时注解到底区别在什么地方?其实说大也不大,主要是考虑到性能上面的问题。运行时注解主要是完全依赖于反射,反射的效率比原生的慢,所以在内存比较少,CPU比较烂的机器上会有一些卡顿现象出现。而编译时注解完全不会有这个问题,因为它在我们编译过程(java->class)中,通过一些注解标示,去动态生成一些类或者文件,所以跟我们的APK运行完全没有任何关系,自然就不存在性能上的问题。所以一般比较著名的开源项目如果采用注解功能,通常采用编译时注解

本文最终demo已经上传到github上,欢迎大家star,follow

唠叨了这么多,我们就学习一下如何自己去实现一个编译时注解功能,因为我想大家日常用这类框架太多了,没必要去教你如何去用了吧

目标

  • 项目1:在编译过程中打印相关自定义信息
  • 项目2 : 实现类似AndroidAnnotations中@EActivity的功能

项目搭建

首先创建一个as的工程,这个没啥好说的。然后我们创建一个注解库。这个库的学问大了,我们不能使用一般的Android Library,而是得使用Java Library,因为我们要使用到javase里面的东西,android.jar里面缺少相应部分。当然你无需担心这些东西在手机里面使用,因为我们仅仅是在电脑上编译需要用到这些

如何实现自定义Java编译时注解功能--初步印象_第1张图片
建立java module后目录结构

这边还有一个地方要注意,我们要在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 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
如何实现自定义Java编译时注解功能--初步印象_第2张图片
javax.annotation.processing.Processor文件位置

OK现在编译一下,就能得到jar包

如何实现自定义Java编译时注解功能--初步印象_第3张图片
得到编译后的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()                              
如何实现自定义Java编译时注解功能--初步印象_第4张图片
编译结果

看到了吧,被注解的元素信息就这样打印出来了

作为第一个项目,咱们就简单的先了解一下这个流程,让它在编译中执行一些代码,下面一篇我们就要真刀真枪的干了。我将结合鸿洋的demo进行说明。这个demo能不能运行我不知道,因为他的项目是Eclipse上的,Android Studio上面的实现步骤与Eclipse还是有区别的,最重要的是demo没有什么注释,没有什么注释,没有什么注释吗,所以正好适合咱们练手

你可能感兴趣的:(如何实现自定义Java编译时注解功能--初步印象)