利用编译时注解生成Java源代码

我们在编写注解的时候,需要指定@Retention,有三个可选值,表示注解会被保留到那个阶段。

RetentionPolicy.SOURCE 
     这种类型的Annotations只在源代码级别保留,编译时就会被忽略,因此一般用来为编译器提供额外信息,以便于检测错误,抑制警告等. 比如@Override @SuppressWarnings

RetentionPolicy.CLASS 
    这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略,一般用来生成源代码,xml文件等
RetentionPolicy.RUNTIME 
    这种类型的Annotations将被JVM保留,所以它们能在运行时被JVM或其他使用反射机制的代码所读取和使用.

编译时注解就是针对Retention=RetentionPolicy.CLASS的情况,目前android上有很多框架都使用了编译时注解,比如Dagger、ButterKnife。利用编译时注解,实现动态的生成代码,大大提升了运行效率。

下面就做个简单的案例:

    通过定义一个BindView注解,用来帮助我们获取view,类似ButterKnife中的BindView注解功能一样。

开发工具使用主流的AndroidStudio。

开发前我们先设计一下module依赖关系:

——InjecterAnnotation

java工程,用来存放注解

——InjecterProcessor

java工程,用来处理我们定义的编译时注解,实现动态生成代码。依赖于InjecterAnnotation

——Injecter

Android library工程,对外提供的注解api,依赖于InjecterAnnotation

——app

Android测试工程,用来测试我们的编译时注解,依赖于Injecter。

一、创建InjecterAnnotation 工程

创建一个java module,定义我们的注解,并指定Retention=RetentionPolicy.CLASS

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}
build.gradle

apply plugin: 'java'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"
}

二、创建InjecterProcessor工程

创建一个java工程,编写我们的注解处理器

我们编写的注解处理器,需要继承javax.annotation.processing.AbstractProcessor

@AutoService(Processor.class)
public final class InjecterProcessor extends AbstractProcessor{

    private Elements elementUtils;
    private Types typeUtils;
    private Filer filer; //生成源代码
    private Messager messager; //打印信息
    private static final ClassName VIEW_BINDER = ClassName.get("injecter.api", "ViewBinder");//实现的接口

    private static final String BINDING_CLASS_SUFFIX = "$$ViewBinder";//生成类的后缀 以后会用反射去取

    @Override public synchronized void init(ProcessingEnvironment env) {
        super.init(env);

        elementUtils = env.getElementUtils();
        typeUtils = env.getTypeUtils();
        filer = env.getFiler();
        messager = env.getMessager();
    }

    /**
     * 需要处理的注解,有多少个就添加多少个
     * @return
     */
    @Override
    public Set getSupportedAnnotationTypes() {
        Set types = new LinkedHashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    /**
     * 支持的源码版本
     * @return
     */
    @Override public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 获取注解信息,动态生成代码
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
       
        //代码放到后面来讲
        return true;
    }
}	
如上所示,一般需要重写getSupportedAnnotationTypes,getSupportedSourceVersion,process

getSupportedAnnotationTypes

  添加我们需要处理的注解

getSupportedSourceVersion

 支持的源代码版本,一般返回SourceVersion.latestSupported()即可。

process

处理注解,动态生成代码,这是重点。


该java module的build.gradle文件定义如下

apply plugin: 'java'

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile project(':InjecterAnnotation')
    compile 'com.google.auto:auto-common:0.6'
    compile 'com.google.auto.service:auto-service:1.0-rc2'
    compile 'com.squareup:javapoet:1.7.0'

    sourceCompatibility = "1.7"
    targetCompatibility = "1.7"
}
1.com.google.auto.service:auto-service:用来帮助我们自动生成META-INF,该目录结构如下

META-INF

     --services

         --javax.annotation.processing.Processor

文件中添加我们的injecter.processor.InjecterProcessor

2.com.squareup:javapoet:java源代码生成工具

三、创建Injecter工程

提供api

public class Injecter {
    public static void bind(Activity activity){
        String clsName = activity.getClass().getName();
        try {
            Class viewBindingClass = Class.forName(clsName + "$$ViewBinder");
            ViewBinder viewBinder = (ViewBinder)viewBindingClass.newInstance();
            viewBinder.bind(activity);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

通过Injecter.bind(activity)实例化动态创建的注解类,完成View的查找

四、Demo

首先在工程根目录中的build.gradle中添加apt支持

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'

        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

在demo module下的build.gradle中添加apt 依赖工程

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

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.3"
    defaultConfig {
        applicationId "com.annotation"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile project(':Injecter')
    //apt
    apt project(':InjecterProcessor')
}


编写测试类

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Injecter.bind(this);
    }
    @BindView(R.id.textview)
    TextView textView;
}

终于完成了所有步骤,下面我们看看编译器是否为我们生存了想要的代码,clean...rebuild project


可以看到build...generated....source目录下多了一个apt目录,且帮我们生存了一个java类 MainActivity$$ViewBinder.

接下来看看MainActivity$$ViewBinder类中都有什么

import android.widget.TextView;

import injecter.api.ViewBinder;

public class MainActivity$$ViewBinder implements ViewBinder {
  @Override
  public void bind(final MainActivity target) {
    target.textView=(TextView)target.findViewById(2131165185);
  }
}
可以看到我们调用Injecter.bind(activity)时,会通过反射创建MainActivity$$ViewBinder 实例,然后调用findViewById生成我们需要的View对象。


到此,就完成了编译时注解的解析,代码生成,注解使用。重点还是在于注解处理器,由于篇幅已经很长,大家直接看代码吧

代码下载






你可能感兴趣的:(【Android,开发篇】)