Android 编译时注解-初认识

背景

编译时注解愈来愈多的出如今各大开源框架使用中,好比javascript

JakeWharton/butterknife viewhtml

greenrobot/EventBus 事件java

square/dagger 依赖注入android

相似这样的库在开发和工做中已经愈来愈多,它们旨在帮助咱们在效率为前提的状况下帮助开发者快速开发,节约时间成本。而它们都使用了编译时注解的思想。git

正由于如此火热,因此有必要好好学习其中的实现原理,方便解决由于编译时注解致使的问题,同时可将此技术运用到本身的开源库中github

思想

编译时注解框架在编写时有相对固定的格式,分包为例api

这里写图片描述

格式相对固定,可是也能够灵活变更,好比讲apiannotations结合在一个moudelapp

moudel中的依赖关系也很是的固定框架

processors依赖包有api- annotationside

app依赖包有 api -annotations-processors

其中除了appandroid moudel之外,其余所有均是java moudel

annotations注解

在讲解annotations注解以前,须要对java和android注解有大体的了解,能够参考我以前的博客

Java-注解详解

Android-注解详解

先初始一个HelloWordAtion注解标注Target为ElementType.TYPE修饰类对象

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface HelloWordAtion {
    String value();
}复制代码

通常一个注解须要对应一个注解处理器,注解处理器在processors处理

processors 注解处理器

对应注解的处理器须要继承AbstractProcessor类,须要复写如下4个方法:

init

init(ProcessingEnvironment processingEnv)会被注解处理工具调用

process

process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)这至关于每一个处理器的主函数main(),你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。

getSupportedAnnotationTypes

etSupportedAnnotationTypes()这里必须指定,这个注解处理器是注册给哪一个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称

@return 注解器所支持的注解类型集合,若是没有这样的类型,则返回一个空集合

getSupportedSourceVersion

指定使用的Java版本,一般这里返回SourceVersion.latestSupported(),默认返回SourceVersion.RELEASE_6 `

@return 使用的Java版本

生成注解处理器

AbstractProcessor有了深刻的了解,知道核心的初始编译时编写代码的方法及时process,在process中咱们经过获得传递过来的数据,写入代码,这里先采用打印的方式,简单输出信息,后续会详细讲解如何本身实现 butterknife功能

public class HelloWordProcessor extends AbstractProcessor {

    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // Filer是个接口,支持经过注解处理器建立新文件
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(HelloWordAtion.class)) {

            if (!(element instanceof TypeElement)) {
                return false;
            }

            TypeElement typeElement = (TypeElement) element;
            String clsNmae = typeElement.getSimpleName().toString();
            String msg = typeElement.getAnnotation(HelloWordAtion.class).value();

            System.out.println("clsName--->"+clsNmae+" msg->"+msg);
        }
        return true;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(HelloWordAtion.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}复制代码

到这一步HelloWordAtion对应的注解处理器已经编写完成,这里简单的打印了HelloWordAtion注解的class和注解指定的value信息

准备工做完成之后,app触发调用

@HelloWordAtion("hello")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}复制代码

这里注解注释的类MainActivity而且指定valuehello,到此准备工做就算完成了,这时若是你直接编译或者运行工程的话,是看不到任何输出信息的,这里还要作的一步操做是指定注解处理器的所在,须要作以下操做:

  • 一、在 processors 库的 main 目录下新建 resources 资源文件夹;

  • 二、在 resources文件夹下创建 META-INF/services 目录文件夹;

  • 三、在 META-INF/services 目录文件夹下建立 javax.annotation.process.Processors 文件;

  • 四、在 javax.annotation.process.Processors 文件写入注解处理器的全称,包括包路径;

经历了以上步骤之后方可成功运行,可是实在是太复杂了,博主为了配置这一步也是搞了很久,因此这里推荐使用开源框架AutoService

AutoService

AutoService

直接在Processors中依赖

compile 'com.google.auto.service:auto-service:1.0-rc2'复制代码

使用

@AutoService(Processor.class)
public class HelloWordProcessor extends AbstractProcessor {
xxxxxxx
}复制代码

到这里运行程序即可以成功看到后台的输出信息

这里写图片描述

须要切换到右下角的Gradle Console窗口,若是变异不成功能够clean工程之后从新运行

获得须要的数据,下一步固然是将数据写入到java class中,也就是题目所言的编译时注解,如何才能写入,这里须要借助Filer

Filer

AbstractProcessorinit方法中初始Filer

private Filer filer;  

    @Override  
    public synchronized void init(ProcessingEnvironment processingEnv) {  
        super.init(processingEnv);    
        filer = processingEnv.getFiler();  
    }复制代码

到此咱们已经有了写入的类的帮助类,还差代码生成逻辑,这里介绍使用javapoet

javapoet

JavaPoet一个是建立 .java 源文件的辅助库,它能够很方便地帮助咱们生成须要的.java 源文件,GitHub上面有很是详细的用法,建议好好阅读相关的使用

javapoet

processors依赖:

compile 'com.squareup:javapoet:1.8.0'复制代码

综合上述的技术,仿照javapoet的第一个Example生成以下代码

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(HelloWordAtion.class)) {

            if (!(element instanceof TypeElement)) {
                return false;
            }

            TypeElement typeElement = (TypeElement) element;
            String clsNmae = typeElement.getSimpleName().toString();
            String msg = typeElement.getAnnotation(HelloWordAtion.class).value();

            System.out.println("clsName--->"+clsNmae+" msg->"+msg);

            // 建立main方法
            MethodSpec main = MethodSpec.methodBuilder("main")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(String[].class, "args")
                    .addStatement("$T.out.println($S)", System.class, clsNmae+"-"+msg)
                    .build();

            // 建立HelloWorld类
            TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(main)
                    .build();

            try {
                // 生成 com.wzgiceman.viewinjector.HelloWorld.java
                JavaFile javaFile = JavaFile.builder("com.wzgiceman.viewinjector", helloWorld)
                        .addFileComment(" This codes are generated automatically. Do not modify!")
                        .build();
                // 生成文件
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }复制代码

这里重点讲解process方法,也就是写入代码的方法体,咱们在javapoetExample基础上将输出信息改成HelloWordAtion注解获取的信息,处处便彻底搞定编译时注解的整个流程,clean之后运行工程,在以下路径下即可看到自动编译生成的HelloWorld

这里写图片描述

到此简单的编译时注解就搞定了,可是编译时注解的自动写入也会致使代码混乱,可能在屡次build编译过程当中出现文件冲突的状况,因此这里须要引入android-apt

android-apt

android-apt能在编译时期去依赖注解处理器并进行工做,但在生成 APK 时不会包含任何遗留无用的文件,辅助 Android Studio项目的对应目录中存放注解处理器在编译期间生成的文件

android-apt

依赖使用:

根目录build.gradle

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'复制代码

app

apply plugin: 'com.neenbedankt.android-apt'

apt project(':processors')复制代码

这里是apt替换compile依赖processors

总结

到此简单的编译时注解就搞定了,可是api模块尚未涉及,别着急接下来的博客中继续扩展,运用掌握的编译时注解和时下主流的butterknife框架,实现一套本身的自定义注入框架中会详细讲解api模块的使用,你会发现原来butterknife很简单,固然能够自由发散,扩展回到本身的任何开源项目中,替换掉反射提升效率。火烧眉毛的小伙伴能够去GitHub下载源码先自行研究。


专栏

注解-编译运行时注解


源码

下载源码


建议

若是你有任何的问题和建议欢迎加入QQ群告诉我!

相关文章
相关标签/搜索