Android注解生成代码
Android注解生成代码老早前就开始用了,在以前Eclipse ADT时代就已经开始使用AvailableAnnotations注解框架,极大简化了Android开发。只是一开始并不了解其原理,感觉这个黑科技还是蛮厉害的,但每次遇到问题总要排查很久。如今Android Studio大行其道,对注解技术支持的更完善,看来有必要学学Java中注解到底是咋回事了。
java是一门静态语言,语言层面缺乏灵活性,这使得我们项目中很容易出现大量重复的代码。java注解是java5引入的功能,我们能够经常看到,如@Override,但未必对其有深入的了解。了解这方面的知识有助于我们深入理解一些框架,下面就以模仿AndroidAnnotations中三个注解特性来详细说明。
- 注解布局文件,@EActivity(layout_id);
- 注解控件,@ViewById;
- Activity初始化,@AfterViews。
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface EActivity {
int value() default -1;
}- @interface,定义一个注解类;
- @Retention,注解的保存策略,有三种:
- SOURCE,源码文件中;
- CLASS,源码文件和class文件中;
- RUNTIME,运行时,这类的注解一般是通过反射解析。
- @Target,注解的目标,可以是 TYPE、Field、METHOD、PARAMETER、CONSTRUCTOR、LOCALVARIABLE、ANNOTATIONTYPE、PACKAGE。
在编译时,扫描和处理注解的一个构建工具,Java5出现,Java6开放,Java7被废弃。现在,该功能由javac来实现,主要由AbstractProcessor处理注解,这个类我们必须了解。在gradle项目中引入apt,只需在项目build.gradle中的dependencies加入依赖:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'AbstractProcessor是javac扫描和处理注解的关键类。必须继承这个抽象类,解析注解,生成源代码。
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
@Override
public Set<String> getSupportedAnnotationTypes() { }
@Override
public SourceVersion getSupportedSourceVersion() { }
}MyProcessor的各个方法的作用:
- init(ProcessingEnvironment env):javac会在Processor创建时调用并执行的初始化操作,该方法会传入一个参数ProcessingEnvironment env,通过env可以访问Elements、Types、Filer等工具类;
- getSupportedAnnotationTypes():获取所有被注解的类型;
- getSupportedSourceVersion:获取源码支持的JDK版本,通常SourceVersion.latestSupported();
- process(Set<? extends TypeElement> annotations, RoundEnvironment env):处理注解的主方法,代码生成的逻辑,参数RoundEnvironment env可以访问任何一个被注解的元素。
生成代码的本质就是拼接字符串,而javapoet为我们提供了一个优雅的拼接方式,极大方便了代码生成。
compile 'com.squareup:javapoet:1.8.0'javapoet项目主页:https://github.com/square/javapoet
如何让javac执行时调用我自定义的 MyProcessor 呢,这需要注册自定义的 MyProcessor
- MyProcessor 需要打包到 jar 包中,就像其它普通的 .jar 文件一样,这里命名为 MyProcessor.jar
- 但 MyProcessor.jar 中多了一个特殊的文件:javax.annotation.processing.Processor,它存储在 jar/META-INF/services/ 文件夹下,MyProcessor.jar 的结构是这样的:
MyProcessor.jar
- com
- goven
- processor
- MyProcessor.class
- META-INF
- services
- javax.annotation.processing.Processorjavax.annotation.processing.Processor 文件列出了要注册的 Processor,每个 Processor 逐行列出
com.goven.annotation.processor.MyProcessor
com.goven.annotation.processor.OtherProcessor
...构建项目时 javac 自动检测并读取 javax.annotation.processing.Processor 来注册列出来的 Processor,然后执行我们生成代码的逻辑。
通过上面的注册流程,发现着实麻烦,还好大Google为我们提供了一个自动注册的库 auto-servic,只需在gradle配置中加入以下依赖:
compile 'com.google.auto.service:auto-service:1.0-rc2'然后在自定义的Processor前加上注解@AutoService(Processor.class)
创建三个模块:
- app,主模块,Activity中演示注解效果
- api,定义注解类,app和processor模块中用到
- processor,解析及生成源代码,编译期用到,运行时不包含进去
在工程根目录的build.gradle中加入apt依赖,构建工程时,它会辅助 javac 执行 processor
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}- 应用android-apt插件
- 依赖processor模块,生成源代码,编译期有效
- 依赖api模块
// 应用android-apt插件
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:24.2.1'
testCompile 'junit:junit:4.12'
// 加入processor和api模块依赖
apt project(':processor')
compile project(':api')
}// activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<TextView
android:id="@+id/tvAnnotation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是一个被注解的控件" />
<TextView
android:id="@+id/tvOther"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="我是另外一个被注解的控件" />
</RelativeLayout>// MainActivity.java
package com.goven.annotation;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import com.goven.api.AfterViews;
import com.goven.api.EActivity;
import com.goven.api.ViewById;
@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@ViewById TextView tvAnnotation;
@ViewById(R.id.tvAnnotation) TextView textView;
@AfterViews void initView() {
}
}// 普通的 java 模板
apply plugin: 'java'注解Activity,注入布局ID,生成 Activity 及 onCreate 方法,注解目标是 ElementType.Type
// EActivity.java
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface EActivity {
int value() default -1;
}注解控件,注入id,生成 findViewById() 方法,注解目标是 ElementType.FIELD
// ViewById.java
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface ViewById {
int value() default -1;
}注解初始化方法,控件注解完成后调用初始化方法,注解目标是 ElementType.METHOD
// AfterViews.java
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface AfterViews {
}- 普通Java模板
- 依赖api模块
- 依赖auto-service,自动生成 javax.annotation.processing.Processor 文件
- 依赖javapoet,生成代码
// 普通的 java 模板
apply plugin: 'java'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
// 加入auto-service和javapoet依赖
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.8.0'
// 依赖api模块
compile project(':api')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
private ProcessorUtil util;// 自定义的注解工具类
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Messager messager = processingEnv.getMessager();
util = new ProcessorUtil(messager);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
// 设置可支持的注解
Set<String> types = new HashSet<>();
types.add(EActivity.class.getCanonicalName());
types.add(ViewById.class.getCanonicalName());
types.add(AfterViews.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 遍历被@EActivity注解的Activity
List<ActivityAnnotation> activitys = null;
for (Element element : roundEnv.getElementsAnnotatedWith(EActivity.class)) {
if (element instanceof TypeElement) {
TypeElement activityElement = (TypeElement) element;
ActivityAnnotation activityAnnotation = new ActivityAnnotation();
activityAnnotation.setActivityElement(activityElement);
List<VariableElement> viewElements = null;
ExecutableElement initElement = null;
// 遍历Activity元素的所有子元素,包括常量、变量、块、构造器、方法等等
for (Element subElement : activityElement.getEnclosedElements()) {
// 只取出被@ViewById注解的变量元素
if (subElement instanceof VariableElement && subElement.getAnnotation(ViewById.class) != null) {
if (viewElements == null) {
viewElements = new ArrayList<>();
}
VariableElement viewElement = (VariableElement) subElement;
viewElements.add(viewElement);
} else if (subElement instanceof ExecutableElement && subElement.getAnnotation(AfterViews.class) != null) {
// 只取出被@AfterViews注解的方法元素
initElement = (ExecutableElement) subElement;
}
}
activityAnnotation.setViewElements(viewElements);
activityAnnotation.setInitElement(initElement);
if (activitys == null) {
activitys = new ArrayList<>();
}
activitys.add(activityAnnotation);
}
}
if (activitys != null) {
try {
generateCode(activitys);
} catch (NoPackageNameException | IOException e) {
util.printMessage("Couldn't generate class");
}
}
return true;
}
/**
* 生成源代码
* @param activitys 被注解的Activity集合
*/
private void generateCode(List<ActivityAnnotation> activitys) throws NoPackageNameException, IOException {
for (ActivityAnnotation annotation : activitys) {
TypeElement activityElement = annotation.getActivityElement();
// 从类型元素中获取注解对象
EActivity activity = activityElement.getAnnotation(EActivity.class);
// 取出Activity类名称
String activityName = activityElement.getSimpleName().toString();
// 取出布局ID
int layoutId = activity.value();
// 包名
String packageName = util.getPackageName(processingEnv.getElementUtils(), activityElement);
// javapoet的类型构建工具,构建Activity类
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(activityName + "_").addModifiers(Modifier.PUBLIC);
typeBuilder.superclass(TypeName.get(activityElement.asType()));
// 方法构建,构建onCreate方法
MethodSpec.Builder createBuilder = MethodSpec.methodBuilder("onCreate")
.addModifiers(Modifier.PROTECTED)
.addParameter(Bundle.class, "savedInstanceState")
.addStatement("super.onCreate(savedInstanceState)")
.addStatement("setContentView($L)", layoutId);
// 构建View控件
if (annotation.getViewElements() != null) {
for (VariableElement viewElement : annotation.getViewElements()) {
String viewName = viewElement.getSimpleName().toString();
ViewById viewById = viewElement.getAnnotation(ViewById.class);
// 被注解的View是否设置了value值,如果设置直接使用此id,否则以view的变量名当作id
if (viewById.value() != -1) {
createBuilder.addStatement("$N = ($T) findViewById($L)", viewName, TypeName.get(viewElement.asType()), viewById.value());
} else {
createBuilder.addStatement("$N = ($T) findViewById(R.id.$N)", viewName, TypeName.get(viewElement.asType()), viewName);
}
}
}
if (annotation.getInitElement() != null) {
createBuilder.addStatement("$N()", annotation.getInitElement().getSimpleName());
}
typeBuilder.addMethod(createBuilder.build());
JavaFile javaFile = JavaFile.builder(packageName, typeBuilder.build()).build();
// 在app/build/generated/source/apt 生成一份源代码
javaFile.writeTo(processingEnv.getFiler());
javaFile.writeTo(System.out);// 输出到终端
}
}
/**
* Activity注解封装类,对应生成一个Activity源文件
*/
private static class ActivityAnnotation {
private TypeElement activityElement;
private List<VariableElement> viewElements;
private ExecutableElement initElement;
public TypeElement getActivityElement() {
return activityElement;
}
public void setActivityElement(TypeElement activityElement) {
this.activityElement = activityElement;
}
public List<VariableElement> getViewElements() {
return viewElements;
}
public void setViewElements(List<VariableElement> viewElements) {
this.viewElements = viewElements;
}
public ExecutableElement getInitElement() {
return initElement;
}
public void setInitElement(ExecutableElement initElement) {
this.initElement = initElement;
}
}
}编译完成后,在processor模块生成processor.jar ,其中关键文件javax.annotation.processing.Processor也被自动添加到jar包中
 {
super.onCreate(savedInstanceState);
setContentView(2130968602);
tvAnnotation = (TextView) findViewById(R.id.tvAnnotation);
textView = (TextView) findViewById(2131427414);
initView();
}
}最后一点,要在AndroidManifest.xml中注册生成的Activity_,而不是被注解的Activity,所有的操作和跳转都是针对Activity_
<activity android:name=".MainActivity_">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SecondActivity_"/>运行效果
至此,基础的注解功能已实现,如果想学习更多的注解,访问: AndroidAnnotations
- 这里只是简单演示注解生成代码的特性,真实的注解框架远比这个要复杂、精细;
- 演示代码中并未检查被注解元素的有效性,比如@EActivity是否真正注解了Activity组件;
- Android环境未封装,未得到R文件,未检查Manifest文件中是否注册了Activity_;
- 编译期注解被称为 pre-compile,不能直接访问 *.class,如果要访问类信息,使用 element.asType();
- 如果想要Debugging注解处理过程,需要另外配置Gradle及AndroidStudio,稍后我会写一篇Debugging Annotation的教程。

