利用編譯時註解生成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
build.gradle@Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) public @interface BindView { int value(); }
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<String> getSupportedAnnotationTypes() {
Set<String> 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
}
在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
接下來看看MainActivity$$ViewBinder類中都有什麼
import android.widget.TextView;
import injecter.api.ViewBinder;
public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<MainActivity> {
@Override
public void bind(final MainActivity target) {
target.textView=(TextView)target.findViewById(2131165185);
}
}
可以看到我們呼叫Injecter.bind(activity)時,會通過反射建立MainActivity$$ViewBinder 例項,然後呼叫findViewById生成我們需要的View物件。
到此,就完成了編譯時註解的解析,程式碼生成,註解使用。重點還是在於註解處理器,由於篇幅已經很長,大家直接看程式碼吧