什么是注解
注解(Annotation),也叫元數(shù)據(jù)(即描述數(shù)據(jù)的數(shù)據(jù)),一種代碼級別的說明。
它是JDK1.5及以后版本引入的一個特性,與類、接口、枚舉是在同一個層次。它可以聲明在包、類、字段、方法、局部變量、方法參數(shù)等的前面,用來對這些元素進行說明,注釋。
起初作用基本類似是加強型的注釋語言,為了編譯檢查,增強可閱讀性,后續(xù)也可以用來生成代碼,起到輔助項目的作用。
基本注解
內(nèi)置注解
java內(nèi)置注解,舉個??:
@Override:讓編譯器檢查被標記的方法,保證其重寫了父類的某一個方法。此注解只能標記方法。源碼如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
元注解
用來給其他注解打標簽的注解,即用來解釋其他注解的注解,舉兩個??:
@Retention:用于指定被此元注解標注的注解的保留時長,即作用域
RetentionPolicy.SOURCE: :在源文件中有效(即源文件保留),例如@Override
RetentionPolicy.CLASS::注解信息保留在class文件中,但是虛擬機不會持有其信息,可用來編譯期注解
RetentionPolicy.RUNTIME::注解信息保留在class文件中,而且虛擬機也會持有此注解信息,所以可以通過反射的方式獲得注解信息,用來運行時注解
編譯期注解: 在class文件中可用,能夠存于編譯之后的字節(jié)碼之中,該注解的注冊信息會保留在.java源碼里和.class文件里,在執(zhí)行的時候,會被Java虛擬機丟棄,不會加載到虛擬機中。
運行期注解:Java虛擬機在運行期保留注解信息,可以通過反射機制讀取注解的信息(.java源碼,.class文件和執(zhí)行的時候都有注解的信息)
@Target:注解對象的作用范圍
CONSTRUCTOR:用于描述構(gòu)造器
FIELD:用于描述字段
LOCAL_VARIABLE:用于描述局部變量
METHOD:用于描述方法
PACKAGE:用于描述包
PARAMETER:用于描述參數(shù)
TYPE:用于描述類、接口(包括注解類型) 或enum聲明
自定義注解
編譯期注解總體結(jié)構(gòu)
編譯時注解 + APT (Annotation Processing Tool)+ JavaPoet(自定義Java源文件) + auto-service(處理器注冊)
APT(Annotation Processing Tool) 是一種處理注釋的工具, 它對源代碼文件進行檢測找出其中的 Annotation,使用 Annotation 進行額外的處理。處理器在處理 Annotation 時可以根據(jù)源文件中的 Annotation 生成額外的源文件和其它的文件 (文件具體內(nèi)容由 Annotation 處理器的編寫者決定),APT 還會編譯生成的源文件和原來的源文件,將它們一起生成 class 文件。
JavaPoet是一款可以自動生成Java文件的第三方庫,簡潔易懂的API,讓繁雜、重復的Java文件,自動化生成,簡化流程。
auto-service:編譯時注解處理器AbstractProcessor需要注冊到JVM中。使用@Autoservice注解就完成了注冊,替代以前的手動配置(??:在Java Library項目中,在resources資源文件夾下創(chuàng)建META-INF.services,然后在該路徑下創(chuàng)建名為javax.annotation.processing.Processor的文件,在該文件中配置(處理器的完整路徑,每行一個)需要啟用的注解處理器)。
編譯期注解處理流程

編譯期注解實踐
寫了ShadowAptDemo,實現(xiàn)類似butterknife @BindView方法,根據(jù)注解自動獲取view對象

app:主項目
apt_knife_api:功能API,給主項目依賴調(diào)用,Android lib
apt_complier:注解處理器,AbstractProcessor是javax.annotation.processing包下的一個抽象類,Android平臺是基于OpenJDK的,而OpenJDK中不包含AbstractProcessor的相關(guān)代碼,所以是一個Java lib
apt_annotations:注解的定義,需要給到注解處理器以及app主項目使用,所以也是Java lib
JDK的版本要一致,不然編譯出錯
注解的定義
apt_annotations:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface ShadowBindView {
int value();
}
定義一個編譯期作用域的注解 ShadowBindView,用于描述字段
里面包含一個方法用于接收ResID
注解處理器
apt_complier:
需要配置相關(guān)三方庫的依賴
dependencies{
//處理器注冊
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
implementation 'com.google.auto:auto-common:0.10'
//java文件生成
implementation 'com.squareup:javapoet:1.11.1'
//注解依賴
implementation project(':apt_annotations')
}
自定義注解處理器
進行相關(guān)變量的初始化
@AutoService(Processor.class) //使用auto進行注冊注解處理器,只需要在這里聲明即可
@SupportedSourceVersion(SourceVersion.RELEASE_7) //指定jdk版本
//聲明需要處理的注解
@SupportedAnnotationTypes("com.example.apt_annotations.ShadowBindView")
public class ShadowProcessor extends AbstractProcessor {
//Processor工具元素
private Elements processorElements;
//生成文件
private Filer filer;
//自定義注解類集合
private Map<String, AnnotationClass> mAnnotatedClassMap;
//注解處理器初始化的時候回調(diào)
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//返回包含用于操作Element的工具方法元素
processorElements = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
mAnnotatedClassMap = new HashMap<>();
}
....
AbstractProcessor是抽象類,有一個必須實現(xiàn)的抽象方法process方法,是注解處理器處理注解時候進行回調(diào),需要在這里進行掃描和處理注解。
返回值邏輯
true,則這些注解已聲明,且不要求后續(xù)Processor處理它們
false,則這些注解未聲明,且可能要求后續(xù)Processor處理它們
RoundEnvironment,是指這輪處理注解所需要的元素信息,常用方法有:
getElementsAnnotatedWith,返回包含指定注解類型的元素的集合
errorRaised,上一輪注解處理器是否產(chǎn)生錯誤
Element,基礎(chǔ)元素, 以下是繼承它的元素
VariableElement ,代表成員變量
ExecutableElement ,代表類中的方法
TypeElement ,代表類
PackageElement ,代表Package
ShadowProcessor
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
System.out.println("process: " + roundEnv.toString());
mAnnotatedClassMap.clear();
//創(chuàng)建自定義注解處理類
try {
processBindView(roundEnv);
} catch (Exception e) {
e.printStackTrace();
}
//將自定義注解處理類,寫入文件
for (AnnotationClass annotationClass : mAnnotatedClassMap.values()) {
try {
annotationClass.generateFiler().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
private void processBindView(RoundEnvironment roundEnv) {
//遍歷包含ShadowBindView注解類型的元素的集合
for (Element element : roundEnv.getElementsAnnotatedWith(ShadowBindView.class)) {
//注解類,AnnotationClass后續(xù)會詳細講到
AnnotationClass annotationClass = createAnnotationClass(element);
//構(gòu)建注解變量類,后續(xù)會詳細講到
BindViewField bindViewField = new BindViewField(element);
//添加對應(yīng)的變量
annotationClass.addField(bindViewField);
System.out.println("processBindView annotatedClass: " + annotationClass);
System.out.println("processBindView bindViewField: " + bindViewField);
}
}
//組建注解類
private AnnotationClass createAnnotationClass(Element element) {
//獲取包含ShadowBindView注解的類元素
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
String fullName = typeElement.getQualifiedName().toString();
System.out.println("createAnnotationClass typeElement: " + typeElement);
AnnotationClass annotationClass = mAnnotatedClassMap.get(fullName);
//集合中不存在,才添加到集合中,去重
if (annotationClass == null) {
//創(chuàng)建注解類 將類元素typeElement,工具元素processorElements傳入
annotationClass = new AnnotationClass(typeElement, processorElements);
mAnnotatedClassMap.put(fullName, annotationClass);
}
return annotationClass;
}
創(chuàng)建注解變量類,用來存儲注解變量數(shù)據(jù)
public class BindViewField {
//變量元素
private VariableElement mVariableElement;
private int mResId;
public BindViewField(Element element) {
//判斷類型,是否是變量元素
if (element.getKind() != ElementKind.FIELD) {
return;
}
mVariableElement = (VariableElement) element;
//獲取自定義注解類型的變量
ShadowBindView bindView = mVariableElement.getAnnotation(ShadowBindView.class);
//獲取view對象Rid
mResId = bindView.value();
}
public int getResId() {
return mResId;
}
public Name getFieldName() {
return mVariableElement.getSimpleName();
}
//變量對應(yīng)的類型
public TypeMirror getFieldType() {
return mVariableElement.asType();
}
}
在注解類之前,先創(chuàng)建一個接口IViewBinder,它是用來銜接api和生成類文件的通信
就定義了兩個需要實際使用的方法,后面構(gòu)建類的時候會實現(xiàn)這個接口
apt_knife_api
public interface IViewBinder<T> {
void bindView(T host);
void unbindView(T host);
}
創(chuàng)建注解類,用來存儲類信息和構(gòu)建類文件
public class AnnotationClass {
static final ClassNameINTERFACE = ClassName.get("com.example.apt_knife_api", "IViewBinder");
//注解類的BindView變量集合
private final ArrayList<BindViewField> mFields;
//注解類的元素
private final TypeElement mTypeElement;
//Processor元素
private final Elements mProcessorElements;
public AnnotationClass(TypeElement typeElement, Elements elements) {
mFields = new ArrayList<>();
mTypeElement = typeElement;
mProcessorElements = elements;
}
/**
* 添加BindView變量
*
* @param field
*/
public void addField(BindViewField field) {
mFields.add(field);
}
/**
* 利用javaPoet生成對應(yīng)的.java代碼
*
* @return
*/
public JavaFile generateFiler() {
//生成java方法bindView
MethodSpec.Builder bindViewBuidler = MethodSpec.methodBuilder("bindView")
.addModifiers(Modifier.PUBLIC)//public
.addAnnotation(Override.class)//接口的復寫方法
.addParameter(TypeName.get(mTypeElement.asType()), "host");//參數(shù)
//添加bindView方法的處理解析
for (BindViewField field : mFields) {
//方法參數(shù)
bindViewBuidler.addStatement("host.$N = ($T)(host.findViewById($L))"
, field.getFieldName()
, ClassName.get(field.getFieldType())
, field.getResId());
}
//生成java方法unbindView:
MethodSpec.Builder unbindViewBuilder = MethodSpec.methodBuilder("unbindView")
.addModifiers(Modifier.PUBLIC)//public
.addAnnotation(Override.class)//接口的復寫方法
.addParameter(TypeName.get(mTypeElement.asType()), "host");//添加參數(shù)
//添加unbindView方法的處理解析
for (BindViewField field : mFields) {
unbindViewBuilder.addStatement("host.$N = null", field.getFieldName());
}
//生成java的類文件(.java的文件),
TypeSpec injectClass = TypeSpec.classBuilder(mTypeElement.getSimpleName() + "$ShadowViewBinder")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(INTERFACE,
TypeName.get(mTypeElement.asType())))//類實現(xiàn)的接口名IViewBinder
.addMethod(bindViewBuidler.build())//添加bindView方法
.addMethod(unbindViewBuilder.build())//添加unbindView方法
.build();
//添加包名 使用傳入的工具類元素獲取包名
String packageName = mProcessorElements.getPackageOf(mTypeElement).getQualifiedName().toString();
//生成JavaFile 然后會用JavaFile.writeTo(filer)寫入文件
return JavaFile.builder(packageName, injectClass).build();
}
}
主要有兩個方法,一個添加構(gòu)建好的變量類,用于生成類文件時候生成變量,最后一個是使用javaPoet生成類文件
使用javaPoet生成類文件的關(guān)鍵步驟注釋都有,有個特別值得關(guān)注的點是構(gòu)建方法代碼的時候
//添加方法代碼
bindViewBuidler.addStatement("host.$N = ($T)(host.findViewById($L))", field.getFieldName() ,
ClassName.get(field.getFieldType()) , field.getResId());
可以看見在寫配置代碼的時候有多個不同的通配符,分別代表不同的含義,可以簡單看一下源碼
private void addArgument(String format, char c, Object arg) {
switch (c) {
//$N表示獲取參數(shù)的name
case 'N':
this.args.add(argToName(arg));
break;
//$L表示字面意義,原樣輸出
case 'L':
this.args.add(argToLiteral(arg));
break;
//$S表示轉(zhuǎn)成字符串
case 'S':
this.args.add(argToString(arg));
break;
//$T表示轉(zhuǎn)成類型,并自動import
case 'T':
this.args.add(argToType(arg));
break;
default:
throw new IllegalArgumentException(
String.format("invalid format string: '%s'", format));
}
}
private String argToName(Object o) {
if (o instanceof CharSequence) return o.toString();
if (o instanceof ParameterSpec) return ((ParameterSpec) o).name;
if (o instanceof FieldSpec) return ((FieldSpec) o).name;
if (o instanceof MethodSpec) return ((MethodSpec) o).name;
if (o instanceof TypeSpec) return ((TypeSpec) o).name;
throw new IllegalArgumentException("expected name but was " + o);
}
調(diào)用API
apt_knife_api:
創(chuàng)建主項目可以調(diào)用的api類ShadowKnife
public class ShadowKnife {
//緩存類,避免重復反射創(chuàng)建
private static final MapbinderMap = new LinkedHashMap<>();
/**
* 注解綁定
*
* @param host 表示注解 View 變量所在的類,也就是注解類 進行綁定的目標對象
*/
public static void bind(Object host) {
String className = host.getClass().getName();
try {
//看下對應(yīng)的ViewBinder是否存在
IViewBinder binder =binderMap.get(className);
if (binder == null) {
//不存在則通過 反射 創(chuàng)建一個 然后存入緩存 這個類是通過javapoet生成的
Class aClass = Class.forName(className + "$ShadowViewBinder");
binder = (IViewBinder) aClass.newInstance();
binderMap.put(className, binder);
}
//使用注解類的進行綁定
if (binder != null) {
binder.bindView(host);
}
} catch (Throwable e) {
e.printStackTrace();
}
}
/**
* 解除注解綁定
*
* @param host
*/
public static void unBind(Object host) {
String className = host.getClass().getName();
IViewBinder binder =binderMap.get(className);
if (binder != null) {
binder.unbindView(host);
}
binderMap.remove(className);
}
}
主項目使用
先進行模塊依賴
dependencies{
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
//注解處理器依賴
annotationProcessor project(':apt_complier')
implementation project(':apt_annotations')
implementation project(':apt_knife_api')
}
測試api代碼
public class MainActivity extends AppCompatActivity {
@ShadowBindView(R.id.text1)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ShadowKnife.bind(this);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "test", Toast.LENGTH_LONG).show();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
ShadowKnife.unBind(this);
}
}
然后進行clean,編譯,看一下是否生成預期所想的代碼
編譯過程打印的日志

查看所生成的代碼

大功告成!