美文网首页Android Android开发Android开发经验谈
如何实现自定义Java编译时注解功能--初步印象

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

作者: 皮球二二 | 来源:发表于2016-06-03 15:57 被阅读2855次

    上一篇文章我们学习了怎样自定义运行时的注解功能,并且简单的实现了类似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 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<? 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
    
    javax.annotation.processing.Processor文件位置

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

    得到编译后的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没有什么注释,没有什么注释,没有什么注释吗,所以正好适合咱们练手

    相关文章

      网友评论

      • d14dafbba6e9:写的太好了
      • 8103964c6493:这是在logcat里打印出来还是?为啥我的logcat里没有呢? :flushed:
        8103964c6493:@r17171709 ok,谢啦
        皮球二二:@十年磨一贱 不是,是编译过程中,在控制台
      • wwwfffk:我build之后也没有生成jar包,只生成了一个temp文件夹
        目录结构是这样的:
        build:
        -classes
        -dependency-cache
        -temp
        并没有生成jar文件
        皮球二二:@uawang 对,他不会自己编译的,要跟主工程一起编译或者单独编译
        wwwfffk:生成了,是需要把这个module加入到主工程的依赖才生成的。
        皮球二二:@a5b88fa52e14 你对module单独编译的吗?
      • 不紧不慢不慌不乱:为什么我编译后没有生成jar包?
        皮球二二:@不紧不慢不慌不乱 jar包在build文件夹下,需要自行拷贝
      • 谈小龙:👍👍太厉害了
        皮球二二:@谈小龙 你才厉害呢😊

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

      本文链接:https://www.haomeiwen.com/subject/xcwkdttx.html