前言
在前面我們學(xué)習(xí)過java的注解的語法胚膊,說明了注解的作用豺撑;前面舉的例子是運(yùn)行時(shí)注解娃循,這樣會(huì)影響我們代碼執(zhí)行的效率;本章我們看看編譯時(shí)注解的使用方式窖剑,可以利用注解在編譯時(shí)生成代碼跃脊,已提高我們的工作效率;其關(guān)鍵就在AbstractProcessor(注解處理器)
示例
廢話不多說苛吱,我們直接看一個(gè)簡(jiǎn)單的示例:
注解處理器
java library
首先創(chuàng)建一個(gè)java library酪术,用來創(chuàng)建我們的注解處理器
Gradle module java-library
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
api 'com.google.auto.service:auto-service:1.0-rc3'
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
定義一個(gè)我們的自定義注解
package com.example.testabstractprocessor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE) //編譯時(shí)起作用
@Target(ElementType.FIELD) //注解作用在field
public @interface TestBindView {
int value() default -1;
}
針對(duì)上面的自定義注解,編寫注解處理器
package com.example.testabstractprocessor;
import com.google.auto.service.AutoService;
import java.io.IOException;
import java.io.Writer;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
@AutoService(Processor.class)
public class TestAbstractProcessor extends AbstractProcessor {
private Filer mFiler;//用于生成java文件
private Messager mMessager; //用于生成日志
private Elements mElements; //用于解讀annation注解對(duì)象的相關(guān)信息的輔助類
public void note(String log){
mMessager.printMessage(Diagnostic.Kind.WARNING,log);
}
public void note(String format,Object ...args){
mMessager.printMessage(Diagnostic.Kind.WARNING,String.format(format,args));
}
@Override
public Set<String> getSupportedAnnotationTypes() {
/*
該方法返回一個(gè)Set<String>翠储,代表ButterKnifeProcessor要處理的注解類的名稱集合绘雁,即 ButterKnife 支持的注解:butterknife-annotations
* */
//表示,自定義的annotation的名稱集合
Set<String> annotations = new LinkedHashSet<>();
annotations.add(TestBindView.class.getCanonicalName());
return annotations;
}
@Override
public SourceVersion getSupportedSourceVersion() {
//返回當(dāng)前系統(tǒng)支持的 java 版本
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
/*
首先 init() 方法完成sdk版本的判斷以及相關(guān)幫助類的初始化援所,幫助類主要有以下幾個(gè):
Elements elementUtils庐舟,注解處理器運(yùn)行掃描源文件時(shí),以獲取元素(Element)相關(guān)的信息住拭。Element 有以下幾個(gè)子類:
包(PackageElement)挪略、類(TypeElement)、成員變量(VariableElement)滔岳、方法(ExecutableElement)
//總之杠娱,是用來表示被標(biāo)注的信息
Types typeUtils,
Filer filer谱煤,用來生成 java 類文件摊求。
Trees trees,
**/
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mMessager = processingEnvironment.getMessager();
mElements = processingEnvironment.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//完成了目標(biāo)類信息的收集并生成對(duì)應(yīng) java 類
Set<? extends Element> TestBindViewElements = roundEnvironment.getElementsAnnotatedWith(TestBindView.class);
for(Element element:TestBindViewElements){
//獲取包名
PackageElement packageElement = mElements.getPackageOf(element);
String pkgName = packageElement.getQualifiedName().toString();
note("package name = %s",pkgName);
//獲得包裝類類名
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
String enclosingName = typeElement.getQualifiedName().toString();
note("Enclosing class name = %s",enclosingName);
//TestBindView是針對(duì)view進(jìn)行注解的
String fieldName = ((VariableElement)element).getSimpleName().toString(); //field的名字
String fieldClassType = ((VariableElement)element).asType().toString(); //field的類型
TestBindView testBindView = element.getAnnotation(TestBindView.class);
int fieldValue = testBindView.value(); //注解的數(shù)據(jù)
note("field %s %s = %d",fieldClassType,fieldName,fieldValue);
//生成輔助文件
createFile(typeElement,fieldClassType,fieldName,fieldValue);
return true;
}
return false;
}
public void createFile(TypeElement enclosingClass,String fieldType,String fieldName,int fieldValue){
String pkgName = mElements.getPackageOf(enclosingClass).getQualifiedName().toString();
String enclosingName = enclosingClass.getQualifiedName().toString();
try {
JavaFileObject javaFileObject = mFiler.createSourceFile(enclosingName+"_TestBindView"
,new Element[]{}); //要生成的java文件
Writer writer = javaFileObject.openWriter();
writer.write(brewCode(pkgName,fieldType,fieldName,fieldValue));
writer.flush();
writer.close(); //創(chuàng)建對(duì)應(yīng)的java文件
} catch (IOException e) {
e.printStackTrace();
}
}
public String brewCode(String pkgName,String fieldType,String fieldName,int fieldValue){
StringBuilder builder = new StringBuilder();
builder.append("package " + pkgName + ";\n\n");
builder.append("http://Auto generated by AbstractProcessor,do not modify!!\n\n");
builder.append("public class MainActivity_TestBindView { \n\n");
builder.append("public static void main(String[] args){ \n");
String info = String.format("%s %s = %d", fieldType, fieldName, fieldValue);
builder.append("System.out.println(\"" + info + "\");\n");
builder.append("}\n");
builder.append("}");
return builder.toString();
}
}
注意點(diǎn)1:
@AutoService(Processor.class) :向javac注冊(cè)我們這個(gè)自定義的注解處理器刘离,這樣室叉,在javac編譯時(shí),才會(huì)調(diào)用到我們這個(gè)自定義的注解處理器方法硫惕。
AutoService這里主要是用來生成
META-INF/services/javax.annotation.processing.Processor文件的茧痕。如果不加上這個(gè)注解,那么恼除,你需要自己進(jìn)行手動(dòng)配置進(jìn)行注冊(cè)踪旷,具體手動(dòng)注冊(cè)方法如下:
1.創(chuàng)建一個(gè)
META-INF/services/javax.annotation.processing.Processor文件,
其內(nèi)容是一系列的自定義注解處理器完整有效類名集合,以換行切割
2.將自定義注解處理器和
META-INF/services/javax.annotation.processing.Processor打包成一個(gè).jar文件埃脏。所以其目錄結(jié)構(gòu)大概如下所示:
MyProcessor.jar
- com
- example
- MyProcessor.class
- META-INF
- services
- javax.annotation.processing.Processor
注意點(diǎn)2:
init(ProcessingEnvironment env):每個(gè)Annotation Processor必須***
有一個(gè)空的構(gòu)造函數(shù) *搪锣。編譯期間秋忙,init()會(huì)自動(dòng)被注解處理工具調(diào)用彩掐,并傳入ProcessingEnviroment參數(shù),通過該參數(shù)可以獲取到很多有用的工具類: Elements (用于存儲(chǔ)被注解的信息), Types , Filer(用于生成java文件) **等等
process(Set<? extends TypeElement> annoations, RoundEnvironment roundEnv):Annotation Processor掃描出的結(jié)果會(huì)存儲(chǔ)進(jìn)roundEnv中灰追,可以在這里獲取到注解內(nèi)容堵幽,編寫你的操作邏輯。注意,process()函數(shù)中不能直接進(jìn)行異常拋出,否則的話,運(yùn)行Annotation Processor的進(jìn)程會(huì)異常崩潰,然后彈出一大堆讓人捉摸不清的堆棧調(diào)用日志顯示.
getSupportedAnnotationTypes(): 該函數(shù)用于指定該自定義注解處理器(Annotation Processor)是注冊(cè)給哪些注解的(Annotation),注解(Annotation)指定必須是完整的包名+類名(eg:com.example.MyAnnotation)
getSupportedSourceVersion():用于指定你的java版本弹澎,一般返回:SourceVersion.latestSupported()朴下。當(dāng)然,你也可以指定具體java版本:
return SourceVersion.RELEASE_7;
經(jīng)過前面3個(gè)步驟后苦蒿,其實(shí)就已經(jīng)算完成了自定義Annotation Processor殴胧。后面要做的就是在源碼里面,在需要的地方寫上我們自定義的注解就行了佩迟。
以上注解處理器就使用完畢团滥,講生成的jar文件cp到app/libs文件夾下,在app端使用
app
Gradle module app
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
annotationProcessor files('libs/TestAbstractProcessor.jar')
}
package com.example.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.example.testabstractprocessor.TestBindView;
public class MainActivity extends AppCompatActivity {
@TestBindView(R.id.textView)
TextView tv; //此處對(duì)TextView使用了注解
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
參考注解處理器(Annotation Processor)簡(jiǎn)析
注意點(diǎn):
- 在 app/build/generated/source/apt/debug下面生成了想要的文件
package com.example.myapplication;
//Auto generated by AbstractProcessor,do not modify!!
public class MainActivity_TestBindView {
public static void main(String[] args){
System.out.println("android.widget.TextView tv = 2131165319");
}
2.執(zhí)行g(shù)radlew build
就可以在Android Studio下的terminal中看到
警告: package name = com.example.myapplication
警告: Enclosing class name = com.example.myapplication.MainActivity
警告: field android.widget.TextView tv = 2131165319
通過Messager在編譯期間打出log
踩坑
總結(jié)下踩過得坑
首先在Android Stuido里面創(chuàng)建一個(gè)java library module
https://blog.csdn.net/u013087553/article/details/71747327
使用創(chuàng)建java library
https://www.cnblogs.com/jpfss/p/9875402.html
java生成jar包
META-INF https://www.cnblogs.com/mq0036/p/8566427.html
錯(cuò)誤: 編碼GBK的不可映射字符
https://www.cnblogs.com/fply/p/8327759.html
module:app
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
api project(path: ':TestAbstractProcessor')
}
這樣配置不行
Annotation processors must be explicitly declared now. The following dependencies on the compile classpath are found to contain annotation processor. Please add them to the annotationProcessor configuration.
Gradle 3.0以后灸姊,先將生成的jar包復(fù)制過去app project下面的libs文件夾
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
annotationProcessor files('libs/TestAbstractProcessor.jar')
}
拓展延伸
以上其實(shí)就是ButterKnife的基本原理力惯;也是利用注解處理器父晶,對(duì)自定義的注解生成對(duì)應(yīng)的java文件诱建;具體可參考ButterKnife 原理解析
總結(jié)來說就是注解處理器+JavaPoet(更方便的生成java文件)
生成類似于
// Generated code from Butter Knife. Do not modify!
package com.example.test1;
import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;
public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
protected T target;
private View view2131165218;
@UiThread
public MainActivity_ViewBinding(final T target, View source) {
this.target = target;
View view;
target.test = Utils.findRequiredViewAsType(source, R.id.test, "field 'test'", TextView.class);
view = Utils.findRequiredView(source, R.id.button, "field 'btn' and method 'btn_click'");
target.btn = Utils.castView(view, R.id.button, "field 'btn'", Button.class);
view2131165218 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.btn_click();
}
});
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.test = null;
target.btn = null;
view2131165218.setOnClickListener(null);
view2131165218 = null;
this.target = null;
}
}
總結(jié)
注解一般有運(yùn)行時(shí)處理和編譯時(shí)處理兩種使用方式,這里我們都學(xué)習(xí)了一遍押袍,可以看出谊惭,注解對(duì)于一些大型框架圈盔,效率工具而言,是一種利器铁蹈;希望有一天自己能根據(jù)工作中的需求用到注解的使用