我们在编写注解的时候,需要指定@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"
}
创建一个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 extends TypeElement> 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
}
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
接下来看看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对象。
到此,就完成了编译时注解的解析,代码生成,注解使用。重点还是在于注解处理器,由于篇幅已经很长,大家直接看代码吧
代码下载