前言
在前面的文章中炒瘸,咱們學(xué)習(xí)了Java類加載千扔、Java反射链患、Java注解总寒,那現(xiàn)在咱們就可以利用所學(xué)搞點(diǎn)事情了踩身,所謂學(xué)以致用邓厕,方為正途重父。
如果想直接閱讀源碼挤牛,請(qǐng)點(diǎn)這里Github
鋪墊
在開(kāi)始搞事情前,咱們還需要了解以下幾個(gè)物件:
- Annotation Processor: 注解處理器
- JavaPoet:Java源碼文件生成者
- javax.lang.model.element:用于解析程序中的元素呵恢,例如:包鞠值、類、方法渗钉、變量
Annotation Processor
注解處理器是在編譯時(shí)用來(lái)掃描和處理注解的工具彤恶。你可以注冊(cè)自己感興趣的注解,程式編譯時(shí)會(huì)將添加注解的元素鳄橘,交由注冊(cè)它的注解處理器來(lái)處理声离。
那咱們?nèi)绾螌?shí)現(xiàn)一個(gè)自己的注解處理器?
- 繼承AbstractProcessor
- 覆蓋getSupportedAnnotationTypes()
- 覆蓋getSupportedSourceVersion()
- 覆蓋process()
AbstractProcessor:抽象注釋處理器瘫怜,為大多數(shù)自定義注釋處理器的超類术徊。
getSupportedAnnotationTypes():這里注冊(cè)你感興趣的注解。它的返回一個(gè)字符串的Set鲸湃,包含注解類型的合法全稱赠涮。
getSupportedSourceVersion():指定使用的Java版本。通常這里返回SourceVersion.latestSupported()暗挑。
process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment):注解處理器的核心方法笋除,在這里進(jìn)行注解掃描、評(píng)估和處理炸裆,以及生成Java文件垃它。
生成Java文件,就交由JavaPoet來(lái)完成
JavaPoet
JavaPoet是一個(gè)用來(lái)生成 .java源文件的工具(由Square提供)烹看。
咱們來(lái)講一下JavaPoet里面常用的幾個(gè)類:
- TypeSpec:表示一個(gè)類国拇、接口或者枚舉聲明
- MethodSpec:表示一個(gè)構(gòu)造函數(shù)或方法聲明
- FieldSpec:表示一個(gè)成員變量、字段聲明
- JavaFile:生成java文件
下面通過(guò)一個(gè)實(shí)例來(lái)說(shuō)明具體使用方式:
private void generateHelloWorld() throws IOException {
MethodSpec mainMethod = MethodSpec.methodBuilder("main")
.addModifiers(new Modifier[]{Modifier.PUBLIC, Modifier.STATIC})
.addParameter(String[].class, "args")
.addStatement("System.out.println(\"Hello World\")")
.build();
FieldSpec androidVersion = FieldSpec.builder(String.class, "androidVer")
.addModifiers(new Modifier[]{Modifier.PRIVATE})
.initializer("$S", "Lollipop")
.build();
TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld")
.addModifiers(new Modifier[]{ Modifier.FINAL, Modifier.PUBLIC})
.addMethod(mainMethod)
.addField(androidVersion)
.build();
JavaFile javaFile = JavaFile.builder("com.hys.test", typeSpec).build();
javaFile.writeTo(System.out);
}
執(zhí)行函數(shù)听系,結(jié)果如下:
package com.hys.test;
import java.lang.String;
public class HelloWorld {
private String androidVer = "Lollipop";
public static void main(String[] args) {
System.out.println("Hello World");
}
}
這里$S占位符贝奇,JavaPoet占位符如下:
- $S:字符串類型占位符
- $T:類型占位符
- $N:名稱占位符(方法名或者變量名等)
- $L:字面常量
這里只是投石問(wèn)路,關(guān)于JavaPoet更多API使用靠胜,請(qǐng)參見(jiàn)其文檔
javax.lang.model.element
Element
用于 Java 的模型元素的接口掉瞳。
- ExecutableElement:表示某個(gè)類或接口的方法、構(gòu)造方法或初始化程序(靜態(tài)或?qū)嵗├四ㄗ⑨岊愋驮?/li>
- PackageElement:表示一個(gè)包程序元素
- TypeElement:表示一個(gè)類或接口程序元素
- TypeParameterElement:表示類陕习、接口、方法或構(gòu)造方法元素的形式類型參數(shù)
- VariableElement:表示一個(gè)字段址愿、enum 常量该镣、方法或構(gòu)造方法參數(shù)、局部變量或異常參數(shù)
通過(guò)Element的getModifiers()獲得元素的修飾符
Modifier
表示程序元素(如類响谓、方法或字段)上的修飾符损合。
以下是常用修飾符:
- ABSTRACT:修飾符 abstract
- FINAL:修飾符 final
- NATIVE:修飾符 native
- PRIVATE:修飾符 private
- PROTECTED:修飾符 protected
- PUBLIC:修飾符 public
- STATIC:修飾符 static
- SYNCHRONIZED:修飾符 synchronized
通過(guò)Element的asType()獲得元素的類型
TypeMirror
表示 Java 編程語(yǔ)言中的類型省艳。這些類型包括基本類型、聲明類型(類和接口類型)嫁审、數(shù)組類型跋炕、類型變量和 null 類型。
通過(guò)TypeMirror的getKind()類型的種類
TypeKind
表示類型的種類律适。
以下是常用的類型:
- ARRAY:數(shù)組類型
- BOOLEAN:基本類型 boolean
- BYTE:基本類型 byte
- CHAR:基本類型 char
- DECLARED:類或接口類型
- DOUBLE:基本類型 double
- ERROR:無(wú)法解析的類或接口類型辐烂。
- EXECUTABLE:方法、構(gòu)造方法或初始化程序
- FLOAT:基本類型 float
- INT:基本類型 int
- LONG:基本類型 long
- NONE:在實(shí)際類型不適合的地方使用的偽類型
- NULL:null 類型
- PACKAGE:對(duì)應(yīng)于包元素的偽類型
- SHORT:基本類型 short
- TYPEVAR:類型變量
- VOID:對(duì)應(yīng)于關(guān)鍵字 void 的偽類型
獲取元素的父元素
通過(guò)Element的getEnclosingElement返回元素的父元素捂贿。
獲取元素上的注解
通過(guò)Element的getAnnotation(Class<A> annotationType)獲得元素上的注解纠修。
了解了上述內(nèi)容,下面咱們開(kāi)始搞事情
創(chuàng)建注解處理器
1.Android Studio的File->New->New module厂僧,如下圖:
2.在彈出的Create New Module對(duì)話框中選擇Java Library扣草,命名為MockButterknife-complier,如下圖:
3.創(chuàng)建注解處理器類吁系,繼承AbstractProcessor德召,覆蓋getSupportedAnnotationTypes()、getSupportedSourceVersion()汽纤、process()三個(gè)方法上岗,如下圖:
4.注冊(cè)注解處理器,在項(xiàng)目下創(chuàng)建resources->META-INF->Services目錄蕴坪,在Services目錄下創(chuàng)建javax.annotation.processing.Processor文件肴掷,如下圖:
5.編輯javax.annotation.processing.Processor文件,添加注解處理器類背传,如下圖:
6.配置注解處理器呆瞻,添加JavaPoet,如下:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.squareup:javapoet:1.8.0'
}
7.創(chuàng)建自定義注解径玖,咱們?cè)谶@里創(chuàng)建兩個(gè)注解:
- BindView注解
package com.hys.mockbutterknife.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
- OnClick注解
package com.hys.mockbutterknife.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface OnClick {
int[] value();
8.注冊(cè)自定義注解到注解處理器痴脾,在AnnotationProcessor添加如下代碼:
private Set<Class<? extends Annotation>> getSupportedAnnotations(){
Set<Class<? extends Annotation>> supportedAnnotations = new LinkedHashSet<>();
supportedAnnotations.add(BindView.class);
supportedAnnotations.add(OnClick.class);
return supportedAnnotations;
}
在getSupportedAnnotationTypes()方法中調(diào)用getSupportedAnnotations(),即將自定義注解注冊(cè)到注解處理器梳星,代碼如下:
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportedAnnotationTypes = new LinkedHashSet<>();
Iterator ite = getSupportedAnnotations().iterator();
while (ite.hasNext()){
Class annotation = (Class<? extends Annotation>)ite.next();
supportedAnnotationTypes.add(annotation.getCanonicalName());
}
return supportedAnnotationTypes;
}
9.上面咱們已經(jīng)注冊(cè)了自定義注解赞赖,接下來(lái)應(yīng)該處理這些注解(啰嗦,不處理冤灾,注冊(cè)它們做啥前域?!)
后面以BindView為例
查找添加注解的元素
Iterator ite = env.getElementsAnnotatedWith(BindView.class).iterator();
驗(yàn)證元素合法性
- 驗(yàn)證元素是否可以訪問(wèn)
private boolean isInaccessible(Element element, String targetThing, Class<? extends Annotation> annotationClass) {
TypeElement enclosingElement = (TypeElement)element.getEnclosingElement();
//檢查元素的訪問(wèn)修飾符
Set<Modifier> modifiers = element.getModifiers();
if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.STATIC)) {
this.error(element, "@%s %s must not be private or static. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
return true;
}
//檢查元素的父元素
if (enclosingElement.getKind() != ElementKind.CLASS) {
this.error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
return true;
}
//檢查父元素的訪問(wèn)修飾符
if (enclosingElement.getModifiers().contains(Modifier.PRIVATE)) {
this.error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName());
return true;
}
return false;
}
- 驗(yàn)證元素所在包的合法性
private boolean isInWrongPackage(Element element, Class<? extends Annotation> annotationClass) {
TypeElement enclosingElement = (TypeElement)element.getEnclosingElement();
String qualifiedName = enclosingElement.getQualifiedName().toString();
//元素的父元素(即元素所在的類)不能在android的系統(tǒng)包中
if (qualifiedName.startsWith("android.")) {
this.error(element, "@%s-annotated class incorrectly in Android framework package. (%s)", annotationClass.getSimpleName(), qualifiedName);
return true;
}
////元素的父元素不能在java的資源包中
else if (qualifiedName.startsWith("java.")) {
this.error(element, "@%s-annotated class incorrectly in Java framework package. (%s)", annotationClass.getSimpleName(), qualifiedName);
return true;
}
return false;
}
- 驗(yàn)證元素類型的合法性
/*
* 遞歸驗(yàn)證
* 以TextView為例:isSubtypeOfType(typeMirror, "android.view.View")
*/
public static boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) {
// 類型相同
if (isTypeEqual(typeMirror, otherType))
return true;
if (typeMirror.getKind() != TypeKind.DECLARED)
return false;
DeclaredType declaredType = (DeclaredType)typeMirror;
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (typeArguments.size() > 0) {
StringBuilder typeString = new StringBuilder(declaredType.asElement().toString());
typeString.append('<');
for(int i = 0; i < typeArguments.size(); ++i) {
if (i > 0) {
typeString.append(',');
}
typeString.append('?');
}
typeString.append('>');
if (typeString.toString().equals(otherType)) {
return true;
}
}
Element element = declaredType.asElement();
if (!(element instanceof TypeElement)) {
return false;
} else {
TypeElement typeElement = (TypeElement)element;
// 獲取元素的父類
TypeMirror superType = typeElement.getSuperclass();
// 檢查父類的類型
if (isSubtypeOfType(superType, otherType)) {
return true;
} else {
Iterator var7 = typeElement.getInterfaces().iterator();
TypeMirror interfaceType;
do {
if (!var7.hasNext()) {
return false;
}
interfaceType = (TypeMirror)var7.next();
} while(!isSubtypeOfType(interfaceType, otherType));
return true;
}
}
}
生成Java源文件
- 生成類
private TypeSpec createTypeSpec(){
// 生成新類名韵吨,原類名+ _ViewBinding
String className = this.encloseingElement.getSimpleName().toString() + "_ViewBinding";
// 獲取父元素的類型全稱
TypeName targetTypeName = TypeName.get(this.encloseingElement.asType());
// 創(chuàng)建類構(gòu)建器
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className)
.addModifiers(new Modifier[]{Modifier.PUBLIC}) // 添加public修飾符
.addField(targetTypeName, "target", new Modifier[]{Modifier.PRIVATE}); // 添加成員變量target
classBuilder.addFields(createFieldForListener());
if(isActivity()){
classBuilder.addMethod(createConstructorForActivity());
} else if(isView()){
classBuilder.addMethod(createConstructorForView());
} else if(isDialog()){
classBuilder.addMethod(createConstructorForDialog());
}
// 默認(rèn)類構(gòu)造器
classBuilder.addMethod(createBindConstructor());
// 生成類
return classBuilder.build();
}
- 生成JavaFile對(duì)象
public JavaFile brewJava() {
String packageName = MoreElements.getPackage(this.encloseingElement).getQualifiedName().toString();
return JavaFile.builder(packageName, createTypeSpec()).build();
}
- 生成Java源文件
...
JavaFile javaFile = bindSet.brewJava();
try{
javaFile.writeTo(this.processingEnv.getFiler());
}catch (IOException ex){
this.error(typeElement, "Unable to write binding for type %s: %s", typeElement, ex.getMessage());
}
...
創(chuàng)建API
注解處理器搞好了匿垄,還需要給用戶提供API,用戶才能使用。
咱們創(chuàng)建一個(gè)新的Module椿疗,Android Studio的File->New->New module漏峰,選擇Android Library,命名為Mockbutterknife-source变丧。
這個(gè)Module主要使用反射技術(shù)芽狗,動(dòng)態(tài)的創(chuàng)建并調(diào)用上文中生成的類(下文中稱為綁定類)。
- 編寫(xiě)API接口(其中之一)
@UiThread
public static void bind(Activity target) {
View sourceView = target.getWindow().getDecorView();
createBinding(target, sourceView);
}
- 動(dòng)態(tài)創(chuàng)建綁定類痒蓬,調(diào)用其構(gòu)造器方法
private static void createBinding(Object target, View source) {
Class<?> targetClass = target.getClass();
// 查找targetClass名稱+_ViewBinding的class文件,加載并返回構(gòu)造器
Constructor constructor = findBindConstructorForClass(targetClass);
if (constructor == null) {
return ;
}
try {
constructor.newInstance(target, source);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InstantiationException e) {
throw new RuntimeException("Unable to invoke " + constructor, e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new RuntimeException("Unable to create binding instance.", cause);
}
}
在APP中使用
- 配置APP滴劲,在build.gradle中添加如下內(nèi)容:
dependencies {
...
annotationProcessor project(':MockButterknife-complier')
implementation project(path: ':MockButterknife-complier')
implementation project(path: ':Mockbutterknife-source')
- 為Activity添加自定義注解
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_click)
TextView tvClick;
@BindView(R.id.tv_dont_click)
TextView tvDontClcik;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MockButterKnife.bind(this);
initData();
}
...
@OnClick(value = {R.id.tv_click, R.id.tv_dont_click})
public void onClick(View view){
if(view.getId() == R.id.tv_click)
new AboutDialog().show(this.getSupportFragmentManager());
else if(view.getId() == R.id.tv_dont_click)
Toast.makeText(this, getString(R.string.main_toast), Toast.LENGTH_SHORT).show();
}
}
- 生成的class文件
package com.hys.annotationprocessortest;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.TextView;
public class MainActivity_ViewBinding {
private MainActivity target;
private View view2131165309;
private View view2131165310;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
this.target.tvClick = (TextView)source.findViewById(2131165309);
this.target.tvDontClcik = (TextView)source.findViewById(2131165310);
this.view2131165309 = source.findViewById(2131165309);
this.view2131165309.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
target.onClick(v);
}
});
this.view2131165310 = source.findViewById(2131165310);
this.view2131165310.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
target.onClick(v);
}
});
}
}
好了攻晒,關(guān)于如何構(gòu)建編譯時(shí)注解解析框架,就先講到這班挖,上述項(xiàng)目的具體代碼在Github鲁捏,感謝你耐心的閱讀。
我是青嵐之峰萧芙,如果讀完后覺(jué)的有所收獲给梅,歡迎點(diǎn)贊加關(guān)注