什么是注解
注解,通俗的來(lái)說(shuō)祸穷,就是像注釋一樣,是由程序員在代碼中加入的一種“標(biāo)注”谓罗,不影響所編寫(xiě)的原有代碼的執(zhí)行粱哼。而這種標(biāo)注(注解)可以被編碼用的IDE、編譯器檩咱、類(lèi)加載器的代理程序揭措、其他第三方工具以及原有代碼運(yùn)行期間讀取和處理,生成一些新的輔助代碼或是提示刻蚯,從而節(jié)省時(shí)間绊含,提升效率。這些工具讀取注解的時(shí)機(jī)是根據(jù)注解的生命周期來(lái)定的,注解的生命周期就是其“存在壽命”炊汹,分為三種:
1躬充,源注解
@Retention(RetentionPolicy.SOURCE)
注解將被編譯器丟棄。如:@Override
2讨便,類(lèi)注解(ButterKnife)
@Retention(RetentionPolicy.CLASS)
注解由編譯器記錄在類(lèi)文件中充甚,但不需要由VM在運(yùn)行時(shí)保留。
3霸褒,運(yùn)行時(shí)注解(EventBus)
@Retention(RetentionPolicy.RUNTIME)
注解由編譯器記錄在類(lèi)文件中伴找,并在運(yùn)行時(shí)由VM保存,因此可以反射性地讀取它們废菱。 如:@Deprecated
APT(Annotation Processing Tool)注解處理器技矮, 是一個(gè)Gradle插件,協(xié)助Android Studio 處理annotation processors,
是一種處理注解的工具殊轴,確切的說(shuō)它是javac的一個(gè)工具衰倦,可以在代碼編譯期解析注解。注解處理器以Java代碼(或者編譯過(guò)的字節(jié)碼)作為輸入旁理,生成.java文件作為輸出樊零。
Android Gradle插件2.2版本發(fā)布后,Android 官方提供了annotationProcessor插件來(lái)代替android-apt孽文,annotationProcessor同時(shí)支持 javac 和 jack 編譯方式驻襟,而android-apt只支持 javac 方式十性。
同時(shí)android-apt作者宣布不在維護(hù),當(dāng)然目前android-apt仍然可以正常運(yùn)行
總體流程:自定義注解->自定義注解處理器(會(huì)用到j(luò)avapoet)->注冊(cè)注解處理器(會(huì)用到auto-service)->編譯生成java代碼
這面我只是簡(jiǎn)單的做了findViewId和onCliclk事件塑悼!
那我們開(kāi)始說(shuō)起:
如圖:
apt_annotation ,一個(gè)Java Library
主要是用來(lái)自定義注解
apt_library,一個(gè)Android Library
主要是用來(lái)寫(xiě)調(diào)用的編譯時(shí)期生成的java代碼的工具類(lèi)
apt_processor ,一個(gè)Java Library
主要是用來(lái)處理編譯時(shí)的注解操作
為什么要建立java Library呢 ?
原因AbstractProcessor不在Android SDK里面楷掉!要是不建立 Java Library 是調(diào)用不到的厢蒜!在java jre中。
首先:在apt_annotation module 建立注解
//編譯時(shí)期注解烹植,作用目標(biāo) 域生明(類(lèi)斑鸦,接口,成員變量草雕,類(lèi)靜態(tài)變量)
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
@Retention(RetentionPolicy.CLASS)
@Target(METHOD)
public @interface OnClick {
int[] value();
}
那注解寫(xiě)好了:
再來(lái):apt_processor module 建立編譯時(shí)注解處理的邏輯
在moudle中添加依賴(lài)
implementation project(':apt_annotation')
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.11.1'
@AutoService(Processor.class)
public class BindViewProcessorByPoet extends AbstractProcessor {
//寫(xiě)入代碼會(huì)用到
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 拿到每個(gè)類(lèi)巷屿,要生成的代碼集合;
Map<TypeElement, List<CodeBlock.Builder>> builderMap = findAndBuilderByTargets(roundEnvironment);
for (TypeElement typeElement : builderMap.keySet()) {
List<CodeBlock.Builder> codeList = builderMap.get(typeElement);
// 去生成對(duì)應(yīng)的 類(lèi)文件墩虹;
BindViewCreatorByPoetHelper.writeBindView(typeElement, codeList, filer);
}
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
types.add(OnClick.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
private Map<TypeElement, List<CodeBlock.Builder>> findAndBuilderByTargets(RoundEnvironment env) {
Map<TypeElement, List<CodeBlock.Builder>> builderMap = new HashMap<>();
// 遍歷帶對(duì)應(yīng)注解的元素嘱巾,就是某個(gè)View對(duì)象
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
//感覺(jué)這里面應(yīng)該是VariableElement
BindViewCreatorByPoetHelper.parseBindView(element, builderMap);
}
// 遍歷帶對(duì)應(yīng)注解的元素,就是某個(gè)方法
for (Element element : env.getElementsAnnotatedWith(OnClick.class)) {
BindViewCreatorByPoetHelper.parseListenerView(element, builderMap);
}
return builderMap;
}
}
這面會(huì)實(shí)現(xiàn)4個(gè)方法:
init:初始化诫钓⊙眩可以得到ProcessingEnviroment,ProcessingEnviroment提供很多有用的工具類(lèi)Elements, Types 和 Filer
getSupportedAnnotationTypes:指定這個(gè)注解處理器是注冊(cè)給哪個(gè)注解的菌湃,這里說(shuō)明是注解BindView和OnClick
getSupportedSourceVersion:指定使用的Java版本问拘,通常這里返回SourceVersion.latestSupported()
process:可以在這里寫(xiě)掃描、評(píng)估和處理注解的代碼惧所,生成Java文件
所以說(shuō)主要的還是
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 拿到每個(gè)類(lèi)骤坐,要生成的代碼集合;
Map<TypeElement, List<CodeBlock.Builder>> builderMap = findAndBuilderByTargets(roundEnvironment);
for (TypeElement typeElement : builderMap.keySet()) {
List<CodeBlock.Builder> codeList = builderMap.get(typeElement);
// 去生成對(duì)應(yīng)的 類(lèi)文件下愈;
BindViewCreatorByPoetHelper.writeBindView(typeElement, codeList, filer);
}
return true;
}
這一方法:
我們?cè)敿?xì)看下
因?yàn)榇蠖紨?shù)代碼里面都是有注釋的:
private Map<TypeElement, List<CodeBlock.Builder>> findAndBuilderByTargets(RoundEnvironment env) {
Map<TypeElement, List<CodeBlock.Builder>> builderMap = new HashMap<>();
// 遍歷帶對(duì)應(yīng)注解的元素纽绍,就是某個(gè)View對(duì)象
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
//感覺(jué)這里面應(yīng)該是VariableElement
BindViewCreatorByPoetHelper.parseBindView(element, builderMap);
}
// 遍歷帶對(duì)應(yīng)注解的元素,就是某個(gè)方法
for (Element element : env.getElementsAnnotatedWith(OnClick.class)) {
BindViewCreatorByPoetHelper.parseListenerView(element, builderMap);
}
return builderMap;
}
Map<TypeElement, List<CodeBlock.Builder>> builderMap = new HashMap<>();
這一個(gè)集合進(jìn)行存儲(chǔ)驰唬,key則是其實(shí)也就是關(guān)聯(lián)Actvity對(duì)象的Element顶岸,value則是寫(xiě)入的代碼集合(一個(gè)類(lèi)維護(hù)一個(gè)生成的代碼塊的集合)
然后分別對(duì)兩個(gè)注解添加代碼集合:
public class BindViewCreatorByPoetHelper {
public static void parseBindView(Element element, Map<TypeElement, List<CodeBlock.Builder>> codeBuilderMap) {
//獲取最外層的類(lèi)名,具體實(shí)際就是關(guān)聯(lián)某個(gè)Activity對(duì)象Element
//因?yàn)榇藭r(shí)的element是VriableElement,所以拿到的Enclosing 就應(yīng)該是Activity對(duì)象
TypeElement classElement = (TypeElement) element.getEnclosingElement();
// 這個(gè)view是哪個(gè)類(lèi) Class(android.widget.TextView)
String viewType = element.asType().toString();
// 注解的值叫编,具體實(shí)際可能就是 R.id.xxx
int value = element.getAnnotation(BindView.class).value();
// 這個(gè)view對(duì)象名稱(chēng)(比如TextView)
String name = element.getSimpleName().toString();
//創(chuàng)建代碼塊
//$L是占位符辖佣,會(huì)把后面的 name 參數(shù)拼接到 $L 所在的地方
CodeBlock.Builder builder = CodeBlock.builder()
.add("target.$L = ", name);
builder.add("($L)target.findViewById($L)", viewType, value);
List<CodeBlock.Builder> codeList = codeBuilderMap.get(classElement);
if (codeList == null) {
codeList = new ArrayList<>();
codeBuilderMap.put(classElement, codeList);
}
codeList.add(builder);
}
public static void parseListenerView(Element element, Map<TypeElement, List<CodeBlock.Builder>> codeBuilderMap) {
//獲取最外層的類(lèi)名,具體實(shí)際就是關(guān)聯(lián)某個(gè)Activity對(duì)象Element
TypeElement classElement = (TypeElement) element.getEnclosingElement();
List<CodeBlock.Builder> codeList = codeBuilderMap.get(classElement);
if (codeList == null) {
codeList = new ArrayList<>();
codeBuilderMap.put(classElement, codeList);
}
//注解的值
int[] annotationValue = element.getAnnotation(OnClick.class).value();
//因?yàn)樽⒔釦Target是Method搓逾,所以這面拿到的就是方法名字的字符串
String name = element.getSimpleName().toString();
//創(chuàng)建代碼塊
for (int value : annotationValue) {
CodeBlock.Builder builder = CodeBlock.builder();
builder.add("target.findViewById($L).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.$L(v); }})", value, name);
codeList.add(builder);
}
}
public static void writeBindView(TypeElement classElement, List<CodeBlock.Builder> codeList, Filer filer) {
// enclosingElement 卷谈,暗指 某個(gè)Activity.
// 先拿到 Activity 所在包名( cn.citytag.aptdemo.Main3Activity)
String packageName = classElement.getQualifiedName().toString();
packageName = packageName.substring(0, packageName.lastIndexOf("."));//(cn.citytag.aptdemo)
// 再拿到Activity類(lèi)名(Main3Activity))
String className = classElement.getSimpleName().toString();
//此元素定義的類(lèi)型
TypeName type = TypeName.get(classElement.asType());
//if (type instanceof ParameterizedTypeName) {
// type = ((ParameterizedTypeName) type).rawType;
//}
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBindingPoet");
MethodSpec.Builder methodSpecBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(type, "target", Modifier.FINAL)
.addParameter(ClassName.get("android.view", "View"), "source", Modifier.FINAL);
for (CodeBlock.Builder codeBuilder : codeList) {
//方法里面 ,代碼是什么
methodSpecBuilder.addStatement(codeBuilder.build());
}
methodSpecBuilder.build();
// 創(chuàng)建類(lèi) MainActivity_ViewBinding
TypeSpec bindClass = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpecBuilder.build())
.build();
try {
// 生成文件
JavaFile javaFile = JavaFile.builder(packageName, bindClass).build();
//將文件寫(xiě)出
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
List<CodeBlock.Builder> codeList = codeBuilderMap.get(classElement);
if (codeList == null) {
codeList = new ArrayList<>();
codeBuilderMap.put(classElement, codeList);
}
都會(huì)加以判斷是否存在此TypeElemen的key霞篡,在進(jìn)行put元素世蔗!
這樣的話代碼集合添加完成之后再進(jìn)行寫(xiě)入端逼,
還是這個(gè)代碼,每一個(gè)TypeElemen對(duì)應(yīng)一個(gè)代碼塊集合進(jìn)行寫(xiě)入代碼污淋;
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 拿到每個(gè)類(lèi)顶滩,要生成的代碼集合;
Map<TypeElement, List<CodeBlock.Builder>> builderMap = findAndBuilderByTargets(roundEnvironment);
for (TypeElement typeElement : builderMap.keySet()) {
List<CodeBlock.Builder> codeList = builderMap.get(typeElement);
// 去生成對(duì)應(yīng)的 類(lèi)文件寸爆;
BindViewCreatorByPoetHelper.writeBindView(typeElement, codeList, filer);
}
return true;
}
public static void writeBindView(TypeElement classElement, List<CodeBlock.Builder> codeList, Filer filer) {
// classElement 礁鲁,就是關(guān)聯(lián)的某個(gè)Activity
// 先拿到 Activity 所在包名( cn.citytag.aptdemo.Main3Activity)
String packageName = classElement.getQualifiedName().toString();
packageName = packageName.substring(0, packageName.lastIndexOf("."));//(cn.citytag.aptdemo)
// 再拿到Activity類(lèi)名(Main3Activity))
String className = classElement.getSimpleName().toString();
//此元素定義的類(lèi)型
TypeName type = TypeName.get(classElement.asType());
//if (type instanceof ParameterizedTypeName) {
// type = ((ParameterizedTypeName) type).rawType;
//}
ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBindingPoet");
MethodSpec.Builder methodSpecBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(type, "target", Modifier.FINAL)
.addParameter(ClassName.get("android.view", "View"), "source", Modifier.FINAL);
for (CodeBlock.Builder codeBuilder : codeList) {
//方法里面 ,代碼是什么
methodSpecBuilder.addStatement(codeBuilder.build());
}
methodSpecBuilder.build();
// 創(chuàng)建類(lèi) MainActivity_ViewBinding
TypeSpec bindClass = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpecBuilder.build())
.build();
try {
// 生成文件
JavaFile javaFile = JavaFile.builder(packageName, bindClass).build();
//將文件寫(xiě)出
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
然后:apt_library module 建立Tools類(lèi)
public class BindViewByPoetTools {
public static void bind(Activity activity) {
//獲取activity的decorView(根view)
View view = activity.getWindow().getDecorView();
bind(activity, view);
}
private static void bind(Object obj, View view) {
String className = obj.getClass().getName();
//找到該activity對(duì)應(yīng)的Bind類(lèi)的名字
String generateClass = className + "_ViewBindingPoet";
//然后調(diào)用Bind類(lèi)的構(gòu)造方法,從而完成activity里view的初始化
try {
Class.forName(generateClass).getConstructor(obj.getClass(), View.class).newInstance(obj, view);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
最后:app module 進(jìn)行綁定注解赁豆,并調(diào)用Tools類(lèi)仅醇!
在app module添加依賴(lài)
implementation project(':apt_annotation')
implementation project(':apt_library')
annotationProcessor project(':apt_processor')
為什么沒(méi)用apt呢!gradle高版本就不用那么麻煩了魔种!直接annotationProcessor這個(gè)就可以在編譯時(shí)處理注解了析二!
public class Main3Activity extends AppCompatActivity {
@BindView(R.id.tv_one)
TextView mTextViewOne;
@BindView(R.id.tv_two)
TextView mTextViewTwo;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
BindViewByPoetTools.bind(this);
mTextViewOne.setText("one");
mTextViewTwo.setText("two");
}
@OnClick({R.id.tv_one, R.id.tv_two})
public void onBtn1Click(View v) {
Toast.makeText(this, "", Toast.LENGTH_SHORT).show();
}
}
最終:ReBuild as 則會(huì)生成如下代碼:
package cn.citytag.aptdemo;
import android.view.View;
public class Main3Activity_ViewBindingPoet {
public Main3Activity_ViewBindingPoet(final Main3Activity target, final View source) {
target.mTextViewOne = (android.widget.TextView)target.findViewById(2131165325);
target.mTextViewTwo = (android.widget.TextView)target.findViewById(2131165326);
target.findViewById(2131165325).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
target.findViewById(2131165326).setOnClickListener(new android.view.View.OnClickListener() { public void onClick(View v) { target.onBtn1Click(v); }});
}
}
介紹下依賴(lài)庫(kù)auto-service:
auto-service的作用是向系統(tǒng)注冊(cè)processor(自定義注解處理器),
在javac編譯時(shí)节预,才會(huì)調(diào)用到我們這個(gè)自定義的注解處理器方法叶摄。
主要是自己建立我沒(méi)有試!這個(gè)具體我也不清楚心铃!
在使用注解處理器需要先聲明准谚,步驟:
1、需要在 processors 庫(kù)的 main 目錄下新建 resources 資源文件夾去扣;
2柱衔、在 resources文件夾下建立 META-INF/services 目錄文件夾;
3愉棱、在 META-INF/services 目錄文件夾下創(chuàng)建 javax.annotation.processing.Processor 文件唆铐;
4、在 javax.annotation.processing.Processor 文件寫(xiě)入注解處理器的全稱(chēng)奔滑,包括包路徑艾岂;
這樣聲明下來(lái)也太麻煩了?這就是用引入auto-service的原因朋其。
通過(guò)auto-service中的@AutoService可以自動(dòng)生成AutoService注解處理器是Google開(kāi)發(fā)的王浴,用來(lái)生成 META-INF/services/javax.annotation.processing.Processor 文件的
介紹下依賴(lài)庫(kù) javapoet:
助于在編譯期間生成java代碼,要不自己StringBuilder拼接很麻煩梅猿!
https://github.com/square/javapoet
如果在as ReBuild的時(shí)候報(bào)這個(gè)問(wèn)題:
錯(cuò)誤: 編碼GBK的不可映射字符
在apt_processor gradle
加入下面代碼氓辣!
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}