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

来源:互联网 发布:西西软件家园 编辑:程序博客网 时间:2024/06/05 05:56

我们在编写注解的时候,需要指定@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

[java] view plain copy
  1. @Target(ElementType.FIELD)  
  2. @Retention(RetentionPolicy.CLASS)  
  3. public @interface BindView {  
  4.     int value();  
  5. }  
build.gradle

[plain] view plain copy
  1. apply plugin: 'java'  
  2.   
  3. dependencies {  
  4.     compile fileTree(dir: 'libs', include: ['*.jar'])  
  5.     sourceCompatibility = "1.7"  
  6.     targetCompatibility = "1.7"  
  7. }  

二、创建InjecterProcessor工程

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

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

[java] view plain copy
  1. @AutoService(Processor.class)  
  2. public final class InjecterProcessor extends AbstractProcessor{  
  3.   
  4.     private Elements elementUtils;  
  5.     private Types typeUtils;  
  6.     private Filer filer; //生成源代码  
  7.     private Messager messager; //打印信息  
  8.     private static final ClassName VIEW_BINDER = ClassName.get("injecter.api""ViewBinder");//实现的接口  
  9.   
  10.     private static final String BINDING_CLASS_SUFFIX = "$$ViewBinder";//生成类的后缀 以后会用反射去取  
  11.   
  12.     @Override public synchronized void init(ProcessingEnvironment env) {  
  13.         super.init(env);  
  14.   
  15.         elementUtils = env.getElementUtils();  
  16.         typeUtils = env.getTypeUtils();  
  17.         filer = env.getFiler();  
  18.         messager = env.getMessager();  
  19.     }  
  20.   
  21.     /** 
  22.      * 需要处理的注解,有多少个就添加多少个 
  23.      * @return 
  24.      */  
  25.     @Override  
  26.     public Set<String> getSupportedAnnotationTypes() {  
  27.         Set<String> types = new LinkedHashSet<>();  
  28.         types.add(BindView.class.getCanonicalName());  
  29.         return types;  
  30.     }  
  31.   
  32.     /** 
  33.      * 支持的源码版本 
  34.      * @return 
  35.      */  
  36.     @Override public SourceVersion getSupportedSourceVersion() {  
  37.         return SourceVersion.latestSupported();  
  38.     }  
  39.   
  40.     /** 
  41.      * 获取注解信息,动态生成代码 
  42.      * @param annotations 
  43.      * @param roundEnv 
  44.      * @return 
  45.      */  
  46.     @Override  
  47.     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
  48.          
  49.         //代码放到后面来讲  
  50.         return true;  
  51.     }  
  52. }     
如上所示,一般需要重写getSupportedAnnotationTypes,getSupportedSourceVersion,process

getSupportedAnnotationTypes

  添加我们需要处理的注解

getSupportedSourceVersion

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

process

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


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

[plain] view plain copy
  1. apply plugin: 'java'  
  2.   
  3. dependencies {  
  4.     compile fileTree(include: ['*.jar'], dir: 'libs')  
  5.     compile project(':InjecterAnnotation')  
  6.     compile 'com.google.auto:auto-common:0.6'  
  7.     compile 'com.google.auto.service:auto-service:1.0-rc2'  
  8.     compile 'com.squareup:javapoet:1.7.0'  
  9.   
  10.     sourceCompatibility = "1.7"  
  11.     targetCompatibility = "1.7"  
  12. }  
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

[java] view plain copy
  1. public class Injecter {  
  2.     public static void bind(Activity activity){  
  3.         String clsName = activity.getClass().getName();  
  4.         try {  
  5.             Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");  
  6.             ViewBinder viewBinder = (ViewBinder)viewBindingClass.newInstance();  
  7.             viewBinder.bind(activity);  
  8.         } catch (ClassNotFoundException e) {  
  9.             e.printStackTrace();  
  10.         } catch (InstantiationException e) {  
  11.             e.printStackTrace();  
  12.         } catch (IllegalAccessException e) {  
  13.             e.printStackTrace();  
  14.         }  
  15.     }  
  16. }  

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

四、Demo

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

[plain] view plain copy
  1. buildscript {  
  2.     repositories {  
  3.         jcenter()  
  4.     }  
  5.     dependencies {  
  6.         classpath 'com.android.tools.build:gradle:2.2.0'  
  7.   
  8.         classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'  
  9.   
  10.     }  
  11. }  
  12.   
  13. allprojects {  
  14.     repositories {  
  15.         jcenter()  
  16.     }  
  17. }  
  18.   
  19. task clean(type: Delete) {  
  20.     delete rootProject.buildDir  
  21. }  

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

[plain] view plain copy
  1. apply plugin: 'com.android.application'  
  2. apply plugin: 'android-apt'  
  3.   
  4. android {  
  5.     compileSdkVersion 24  
  6.     buildToolsVersion "24.0.3"  
  7.     defaultConfig {  
  8.         applicationId "com.annotation"  
  9.         minSdkVersion 15  
  10.         targetSdkVersion 24  
  11.         versionCode 1  
  12.         versionName "1.0"  
  13.     }  
  14.     buildTypes {  
  15.         release {  
  16.             minifyEnabled false  
  17.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
  18.         }  
  19.     }  
  20. }  
  21.   
  22. dependencies {  
  23.     compile fileTree(include: ['*.jar'], dir: 'libs')  
  24.     compile project(':Injecter')  
  25.     //apt  
  26.     apt project(':InjecterProcessor')  
  27. }  


编写测试类

[java] view plain copy
  1. public class MainActivity extends Activity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.         Injecter.bind(this);  
  8.     }  
  9.     @BindView(R.id.textview)  
  10.     TextView textView;  
  11. }  

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


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

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

[java] view plain copy
  1. import android.widget.TextView;  
  2.   
  3. import injecter.api.ViewBinder;  
  4.   
  5. public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<MainActivity> {  
  6.   @Override  
  7.   public void bind(final MainActivity target) {  
  8.     target.textView=(TextView)target.findViewById(2131165185);  
  9.   }  
  10. }  
可以看到我们调用Injecter.bind(activity)时,会通过反射创建MainActivity$$ViewBinder 实例,然后调用findViewById生成我们需要的View对象。


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

代码下载