經過以上的分析咱們就能夠明確如下 極簡 步驟:java
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
複製代碼
順手Sync下android
implementation project(path: ':annotations')
annotationProcessor project(path: ':annotation_compiler')
複製代碼
annotation_compiler module 依賴數組
implementation project(path: ':annotations')
複製代碼
準備工做到此結束bash
先看結構再看碼app
在 annotation module 中新建兩個註解類
ide
備註:gradle
每個帶有 @BindView 、@OnClick 註解聲明的Activity類都要相應的生成一個XXActivity_ViewBinding類,並在類中聲明一個 bind(Activity target) 方法,該方法可以接收 不一樣的Activity對象;ui
註解處理類的功能纔是真正用來進行註解處理而且建立咱們須要的java類文件的
a. 在module(annotation_compiler)下新建AnnotationComPiler類google
@AutoService(Processor.class) //註冊註解處理器,此處用到環境配置中依賴的AutoService庫
public class AnnotationCompiler extends AbstractProcessor {}
複製代碼
b. 聲明Filer對象
注意:Filer對象配合Writer對象能夠建立一個有內容的文件spa
//聲明文件對象(成員變量)
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//就是這麼寫的,別問
filer = processingEnvironment.getFiler();
}
複製代碼
c. 重寫getSupportedAnnotationTypes(),篩選咱們的目標註解
/** * 聲明註解處理器要處理的註解 */
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
//getCanonicalName()獲取的是包名+類名
types.add(BindView.class.getCanonicalName());
types.add(OnClick.class.getCanonicalName());
return types;
}
複製代碼
d. 重寫getSupportedSourceVersion(),聲明支持的java版本
這裏咱們用默認就能夠
/** * 聲明註解處理器支持的java版本 */
@Override
public SourceVersion getSupportedSourceVersion() {
//processingEnv這是父類變量
return processingEnv.getSourceVersion();
}
複製代碼
d. 重寫process(),生成XXActivity_ViewBinding類文件
首先看一下咱們須要的XXActivity_ViewBinding類的樣子
//聲明包路徑
package com.junt.annotationdemo;
//因爲實現了Binder接口,因此要聲明導入Binder
import com.junt.annotationdemo.Binder;
//點擊事件須要實例化View.OnClickListener()接口,因此還要聲明導入View包
import android.view.View;
public class MainActivity_ViewBinding implements Binder<com.junt.annotationdemo.MainActivity>{
@Override
public void bind(final com.junt.annotationdemo.MainActivity target) {
//首先對於即用到了@BindView又用到了@OnClick的控件,咱們在生成代碼時應該須要如下內容
//textView-說明咱們須要獲取控件名,
//(android.widget.TextView)-說明咱們還須要知道控件的類型
//2131165319-控件的Id
target.textView=(android.widget.TextView)target.findViewById(2131165319);
target.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
target.onClick(view);
} catch (Exception e) {
e.printStackTrace();
}
}
});
//其次,對於僅使用了@OnClick的控件,僅須要一個Id便可
target.findViewById(2131165320).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
target.onClick(view);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
複製代碼
在生成的java類文件中寫入代碼說明
好比咱們須要寫入
package com.junt.annotationdemo;
複製代碼
只須要調用
writer.write("package com.junt.annotationdemo;\n")
複製代碼
所有流程代碼
/**
* 寫一個自動findViewById的文件
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//獲取全部用到@BindView的節點元素
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//根據節點所在的Activity進行分類(一個Activity中會有多個@BindBiew),便於接下來建立每個單獨的XXActivity_ViewBinding類文件
Map<String, List<VariableElement>> mapBindView = new HashMap<>();
for (Element element : elementsAnnotatedWith) {
//獲取控件元素
VariableElement variableElement = (VariableElement) element;
//獲取控件元素的父元素Name(activity或fragment)
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
List<VariableElement> variableElements = mapBindView.get(activityName);
if (variableElements == null) {
variableElements = new ArrayList<>();
mapBindView.put(activityName, variableElements);
}
variableElements.add(variableElement);
}
//獲取全部用到@onClick的節點
Set<? extends Element> elementsAnnotatedWithClick=roundEnvironment.getElementsAnnotatedWith(OnClick.class);
//一樣根據Activity進行分類(一個Activity中僅有一個@OnClick)
Map<String, Element> mapClickView = new HashMap<>();
for (Element element : elementsAnnotatedWithClick) {
String activityName = element.getEnclosingElement().getSimpleName().toString();
mapClickView.put(activityName, element);
}
//開始建立XXActivity_ViewBinding類文件
Writer writer = null;
//用For循環,每一次循環寫一個xxActivity_ViewBinding
for (String activityName : mapBindView.keySet()) {
//取出activityName下的帶註解控件元素
List<VariableElement> variableElements = mapBindView.get(activityName);
//獲取activity所在包名(同一個Activity下的元素的父元素都是該Activity,因此任意取一個就行,這裏取0)
Element enclosingElement = variableElements.get(0).getEnclosingElement();
String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
try {
//實例化一個JavaFileObject對象(咱們寫的是.java文件--類文件)
JavaFileObject sourceFile = filer.createSourceFile(
packageName + "." + activityName + "_ViewBinding");
//實例化writer對象用來寫入向.java文件中寫入代碼
writer = sourceFile.openWriter();
//1.寫入-導包代碼
writer.write("package " + packageName + ";\n");
writer.write("import " + packageName + ".Binder;\n");
writer.write("import android.view.View;\n");
//2.寫入-聲明類及實現接口代碼
writer.write("public class " + activityName + "_ViewBinding implements Binder<"
+ packageName + "." + activityName + ">{\n");
//3.寫入-實現接口方法代碼
writer.write(" @Override\n" +
" public void bind(final " + packageName + "." + activityName + " target) {\n");
//取出帶onCLick註解的元素Id
Element clickVariableElement = mapClickView.get(activityName);
int[] clickIds = clickVariableElement.getAnnotation(OnClick.class).value();
List<Integer> id = new ArrayList<>(clickIds.length);
//4.寫入-全部控件findViewById代碼
for (VariableElement variableElement : variableElements) {
//獲取控件Name
String variableName = variableElement.getSimpleName().toString();
//獲取控件ID
int variableId = variableElement.getAnnotation(BindView.class).value();
//獲取控件Type
TypeMirror typeMirror = variableElement.asType();
//寫FindViewById
writeFindViewById(writer, variableName, typeMirror, variableId);
//同時看看這個控件元素有沒有@OnClick註解,有的話同時寫setOnClickListener
if (contains(clickIds, variableId)) {
//已經設置過點擊事件的全部控件Id
id.add(variableId);
writeSetOnClickListener(writer, packageName, activityName, variableName);
}
}
for (int clickId : clickIds) {
//若是id集合中沒有這個clickId則說明這個空間須要設置點擊事件,但還沒設置點擊事件
if (!id.contains(clickId)) {
writeSetOnClickListenerWithoutName(writer, packageName, activityName, clickId);
}
}
//5.寫入-補全類的構造
writer.write(" \n }\n}\n");
} catch (Exception e) {
e.printStackTrace();
} finally {
//這個Activity已經設置過Id綁定與點擊事件、故移除
mapClickView.remove(activityName);
if (writer != null) {
try {
writer.close();
writer = null;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//處理Activity中僅有@OnClick綁定的狀況,上面的步驟僅是處理了全部含有@BindView的Activity(點擊事件也同時處理而且從mapClickView中移除)
if (mapClickView.size() <= 0) {
return false;
}
for (String activityName : mapClickView.keySet()) {
Element element = mapClickView.get(activityName);
String packageName = processingEnv.getElementUtils().getPackageOf(element.getEnclosingElement()).toString();
try {
JavaFileObject sourceFile = filer.createSourceFile(
packageName + "." + activityName + "_ViewBinding");
writer = sourceFile.openWriter();
//1.寫入-導包代碼
writer.write("package " + packageName + ";\n");
writer.write("import " + packageName + ".Binder;\n");
writer.write("import android.view.View;\n");
//2.寫入-聲明類及實現接口代碼
writer.write("public class " + activityName + "_ViewBinding implements Binder<"
+ packageName + "." + activityName + ">{\n");
//3.寫入-實現接口方法代碼
writer.write(" @Override\n" +
" public void bind(final " + packageName + "." + activityName + " target) {\n");
//取出改Activity下全部@OnClick的Id
int[] clickViewIds = element.getAnnotation(OnClick.class).value();
//4.寫入-setOnClickListener代碼
for (int clickViewId : clickViewIds) {
writeSetOnClickListenerWithoutName(writer, packageName, activityName, clickViewId);
}
//5.補全類構造
writer.write(" \n }\n}\n");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (writer!=null){
writer.close();
writer = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* 寫入findViewById代碼
* @param writer writer
* @param variableName 控件名
* @param typeMirror 控件類型
* @param variableId 控件Id
* @throws IOException 寫入異常
*/
private void writeFindViewById(Writer writer, String variableName, TypeMirror typeMirror, int variableId) throws IOException {
writer.write(" target." + variableName + "=(" + typeMirror + ")target.findViewById(" + variableId + ");\n");
}
/**
* 已經調用過finfViewById()方法的控件設置點擊事件
* @param writer writer
* @param variableName 控件名
* @throws IOException 寫入異常
*/
private void writeSetOnClickListener(Writer writer, String packageName, String className, String variableName) throws IOException {
writer.write(" target." + variableName + ".setOnClickListener(new View.OnClickListener() {\n" +
" @Override\n" +
" public void onClick(View view) {\n" +
" try {\n" +
" target.onClick(view);\n" +
" } catch (Exception e) {\n" +
" e.printStackTrace();\n" +
" }\n" +
" }\n" +
" });\n");
}
/**
* 沒有調用過findViewById()方法的控件設置點擊事件
* @param writer writer
* @param viewId 控件Id
* @throws IOException 寫入異常
*/
private void writeSetOnClickListenerWithoutName(Writer writer, String packageName, String className, int viewId) throws IOException {
writer.write(" target.findViewById(" + viewId + ")" + ".setOnClickListener(new View.OnClickListener() {\n" +
" @Override\n" +
" public void onClick(View view) {\n" +
" try {\n" +
" target.onClick(view);\n" +
" } catch (Exception e) {\n" +
" e.printStackTrace();\n" +
" }\n" +
" }\n" +
" });\n");
}
/**
* 判斷數組中是否含有某個值
* 這裏用來判斷,全部設置@OnClick註解的控件Id中是否也同時設置了@BindView
* @param arr 某一個Activity下全部設置了@OnClick的控件Id
* @param arg 某一個Activity下設置了@BindView的控件Id
* @return true/false
*/
private boolean contains(int[] arr, int arg) {
boolean isContain = false;
if (arr.length == 0) {
isContain = false;
} else {
for (int i : arr) {
if (i == arg) {
isContain = true;
break;
}
}
}
return isContain;
}
複製代碼
module(app)新建一個ButterKnife類
在Activity中使用