什么是注解
注解,通俗的來說,就是像注釋一樣,是由程序員在代碼中加入的一種“標注”,不影響所編寫的原有代碼的執行。而這種標注(注解)可以被編碼用的IDE、編譯器、類加載器的代理程序、其他第三方工具以及原有代碼運行期間讀取和處理,生成一些新的輔助代碼或是提示,從而節省時間,提升效率。這些工具讀取注解的時機是根據注解的生命周期來定的,注解的生命周期就是其“存在壽命”,分為三種:
1,源注解
@Retention(RetentionPolicy.SOURCE)
注解將被編譯器丟棄。如:@Override
2,類注解(ButterKnife)
@Retention(RetentionPolicy.CLASS)
注解由編譯器記錄在類文件中,但不需要由VM在運行時保留。
3,運行時注解(EventBus)
@Retention(RetentionPolicy.RUNTIME)
注解由編譯器記錄在類文件中,并在運行時由VM保存,因此可以反射性地讀取它們。 如:@Deprecated
APT(Annotation Processing Tool)注解處理器, 是一個Gradle插件,協助Android Studio 處理annotation processors,
是一種處理注解的工具,確切的說它是javac的一個工具,可以在代碼編譯期解析注解。注解處理器以Java代碼(或者編譯過的字節碼)作為輸入,生成.java文件作為輸出。
Android Gradle插件2.2版本發布后,Android 官方提供了annotationProcessor插件來代替android-apt,annotationProcessor同時支持 javac 和 jack 編譯方式,而android-apt只支持 javac 方式。
同時android-apt作者宣布不在維護,當然目前android-apt仍然可以正常運行
總體流程:自定義注解->自定義注解處理器(會用到javapoet)->注冊注解處理器(會用到auto-service)->編譯生成java代碼
這面我只是簡單的做了findViewId和onCliclk事件!
那我們開始說起:
如圖:
apt_annotation ,一個Java Library
主要是用來自定義注解
apt_library,一個Android Library
主要是用來寫調用的編譯時期生成的java代碼的工具類
apt_processor ,一個Java Library
主要是用來處理編譯時的注解操作
為什么要建立java Library呢 ?
原因AbstractProcessor不在Android SDK里面!要是不建立 Java Library 是調用不到的!在java jre中。
首先:在apt_annotation module 建立注解
//編譯時期注解,作用目標 域生明(類,接口,成員變量,類靜態變量)
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
@Retention(RetentionPolicy.CLASS)
@Target(METHOD)
public @interface OnClick {
int[] value();
}
那注解寫好了:
再來:apt_processor module 建立編譯時注解處理的邏輯
在moudle中添加依賴
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 {
//寫入代碼會用到
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) {
// 拿到每個類,要生成的代碼集合;
Map<TypeElement, List<CodeBlock.Builder>> builderMap = findAndBuilderByTargets(roundEnvironment);
for (TypeElement typeElement : builderMap.keySet()) {
List<CodeBlock.Builder> codeList = builderMap.get(typeElement);
// 去生成對應的 類文件;
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<>();
// 遍歷帶對應注解的元素,就是某個View對象
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
//感覺這里面應該是VariableElement
BindViewCreatorByPoetHelper.parseBindView(element, builderMap);
}
// 遍歷帶對應注解的元素,就是某個方法
for (Element element : env.getElementsAnnotatedWith(OnClick.class)) {
BindViewCreatorByPoetHelper.parseListenerView(element, builderMap);
}
return builderMap;
}
}
這面會實現4個方法:
init:初始化。可以得到ProcessingEnviroment,ProcessingEnviroment提供很多有用的工具類Elements, Types 和 Filer
getSupportedAnnotationTypes:指定這個注解處理器是注冊給哪個注解的,這里說明是注解BindView和OnClick
getSupportedSourceVersion:指定使用的Java版本,通常這里返回SourceVersion.latestSupported()
process:可以在這里寫掃描、評估和處理注解的代碼,生成Java文件
所以說主要的還是
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 拿到每個類,要生成的代碼集合;
Map<TypeElement, List<CodeBlock.Builder>> builderMap = findAndBuilderByTargets(roundEnvironment);
for (TypeElement typeElement : builderMap.keySet()) {
List<CodeBlock.Builder> codeList = builderMap.get(typeElement);
// 去生成對應的 類文件;
BindViewCreatorByPoetHelper.writeBindView(typeElement, codeList, filer);
}
return true;
}
這一方法:
我們詳細看下
因為大都數代碼里面都是有注釋的:
private Map<TypeElement, List<CodeBlock.Builder>> findAndBuilderByTargets(RoundEnvironment env) {
Map<TypeElement, List<CodeBlock.Builder>> builderMap = new HashMap<>();
// 遍歷帶對應注解的元素,就是某個View對象
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
//感覺這里面應該是VariableElement
BindViewCreatorByPoetHelper.parseBindView(element, builderMap);
}
// 遍歷帶對應注解的元素,就是某個方法
for (Element element : env.getElementsAnnotatedWith(OnClick.class)) {
BindViewCreatorByPoetHelper.parseListenerView(element, builderMap);
}
return builderMap;
}
Map<TypeElement, List<CodeBlock.Builder>> builderMap = new HashMap<>();
這一個集合進行存儲,key則是其實也就是關聯Actvity對象的Element,value則是寫入的代碼集合(一個類維護一個生成的代碼塊的集合)
然后分別對兩個注解添加代碼集合:
public class BindViewCreatorByPoetHelper {
public static void parseBindView(Element element, Map<TypeElement, List<CodeBlock.Builder>> codeBuilderMap) {
//獲取最外層的類名,具體實際就是關聯某個Activity對象Element
//因為此時的element是VriableElement,所以拿到的Enclosing 就應該是Activity對象
TypeElement classElement = (TypeElement) element.getEnclosingElement();
// 這個view是哪個類 Class(android.widget.TextView)
String viewType = element.asType().toString();
// 注解的值,具體實際可能就是 R.id.xxx
int value = element.getAnnotation(BindView.class).value();
// 這個view對象名稱(比如TextView)
String name = element.getSimpleName().toString();
//創建代碼塊
//$L是占位符,會把后面的 name 參數拼接到 $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) {
//獲取最外層的類名,具體實際就是關聯某個Activity對象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();
//因為注解@Target是Method,所以這面拿到的就是方法名字的字符串
String name = element.getSimpleName().toString();
//創建代碼塊
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 ,暗指 某個Activity.
// 先拿到 Activity 所在包名( cn.citytag.aptdemo.Main3Activity)
String packageName = classElement.getQualifiedName().toString();
packageName = packageName.substring(0, packageName.lastIndexOf("."));//(cn.citytag.aptdemo)
// 再拿到Activity類名(Main3Activity))
String className = classElement.getSimpleName().toString();
//此元素定義的類型
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();
// 創建類 MainActivity_ViewBinding
TypeSpec bindClass = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpecBuilder.build())
.build();
try {
// 生成文件
JavaFile javaFile = JavaFile.builder(packageName, bindClass).build();
//將文件寫出
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);
}
都會加以判斷是否存在此TypeElemen的key,在進行put元素!
這樣的話代碼集合添加完成之后再進行寫入,
還是這個代碼,每一個TypeElemen對應一個代碼塊集合進行寫入代碼;
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 拿到每個類,要生成的代碼集合;
Map<TypeElement, List<CodeBlock.Builder>> builderMap = findAndBuilderByTargets(roundEnvironment);
for (TypeElement typeElement : builderMap.keySet()) {
List<CodeBlock.Builder> codeList = builderMap.get(typeElement);
// 去生成對應的 類文件;
BindViewCreatorByPoetHelper.writeBindView(typeElement, codeList, filer);
}
return true;
}
public static void writeBindView(TypeElement classElement, List<CodeBlock.Builder> codeList, Filer filer) {
// classElement ,就是關聯的某個Activity
// 先拿到 Activity 所在包名( cn.citytag.aptdemo.Main3Activity)
String packageName = classElement.getQualifiedName().toString();
packageName = packageName.substring(0, packageName.lastIndexOf("."));//(cn.citytag.aptdemo)
// 再拿到Activity類名(Main3Activity))
String className = classElement.getSimpleName().toString();
//此元素定義的類型
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();
// 創建類 MainActivity_ViewBinding
TypeSpec bindClass = TypeSpec.classBuilder(bindingClassName.simpleName())
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpecBuilder.build())
.build();
try {
// 生成文件
JavaFile javaFile = JavaFile.builder(packageName, bindClass).build();
//將文件寫出
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
然后:apt_library module 建立Tools類
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對應的Bind類的名字
String generateClass = className + "_ViewBindingPoet";
//然后調用Bind類的構造方法,從而完成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 進行綁定注解,并調用Tools類!
在app module添加依賴
implementation project(':apt_annotation')
implementation project(':apt_library')
annotationProcessor project(':apt_processor')
為什么沒用apt呢!gradle高版本就不用那么麻煩了!直接annotationProcessor這個就可以在編譯時處理注解了!
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 則會生成如下代碼:
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); }});
}
}
介紹下依賴庫auto-service:
auto-service的作用是向系統注冊processor(自定義注解處理器),
在javac編譯時,才會調用到我們這個自定義的注解處理器方法。
主要是自己建立我沒有試!這個具體我也不清楚!
在使用注解處理器需要先聲明,步驟:
1、需要在 processors 庫的 main 目錄下新建 resources 資源文件夾;
2、在 resources文件夾下建立 META-INF/services 目錄文件夾;
3、在 META-INF/services 目錄文件夾下創建 javax.annotation.processing.Processor 文件;
4、在 javax.annotation.processing.Processor 文件寫入注解處理器的全稱,包括包路徑;
這樣聲明下來也太麻煩了?這就是用引入auto-service的原因。
通過auto-service中的@AutoService可以自動生成AutoService注解處理器是Google開發的,用來生成 META-INF/services/javax.annotation.processing.Processor 文件的
介紹下依賴庫 javapoet:
助于在編譯期間生成java代碼,要不自己StringBuilder拼接很麻煩!
https://github.com/square/javapoet
如果在as ReBuild的時候報這個問題:
錯誤: 編碼GBK的不可映射字符
在apt_processor gradle
加入下面代碼!
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}