文章純屬個人學(xué)習(xí)的代碼實現(xiàn)
網(wǎng)易云微專業(yè)公開課這節(jié)課 用
JavaPoet簡單實現(xiàn)了ButterKnife的BindView功能
原理其實就是通過注解處理器處理注解,然后利用 JavaPoet面向?qū)ο蟮恼Z法動態(tài)生成了一個Java文件
這里加入我們希望完成MainActivity的BindView功能,MainActivity的代碼如下
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
tv.setText("Hello android");
}
}
我們要看到兩個需要注意的地方,第一個就是
@BindView(R.id.tv)
TextView tv;
這里就是我們要綁定的視圖
第二個地方是這里
ButterKnife.bind(this);
我們要用注解處理器和JavaPoet生成這樣的文件
public class MainActivity$$ViewBinder implements ViewBinder<MainActivity> {
@Override
public void bind(MainActivity target) {
target.tv = target.findViewById(2131165359);
}
}
這里可以看到我們生成了一個以MainActivity+$$ViewBinder的一個 java文件,他需要實現(xiàn)一個接口ViewBinder<MainActivity>
我們可以看看ViewBinder<MainActivity>這個接口
public interface ViewBinder<T> {
void bind(T target);
}
這里聲明了一個泛型接口,表示他 接受任何泛型對象,不同泛型對象實現(xiàn)不同的bind(T target)
方法,比如我們剛剛看到的MainActivity$$ViewBinder類實現(xiàn)的
@Override
public void bind(MainActivity target) {
target.tv = target.findViewById(2131165359);
}
這里傳入的類型是MainActivity
然后我們可以看到這個文件其實就是幫我們做了一個操作,利用傳進(jìn)來的MainActivity完成了tv控件的綁定 ,記住了這里的代碼不用自己寫,是注解處理器和JavaPoet生成的。
但是看到這里有同學(xué)要問了,你生成了這個文件,但是我沒有調(diào)用呀?嗯,問的很好,這里我們還記得
ButterKnife.bind(this);
嗎,我們來看看他的代碼
public class ButterKnife {
//target.tv=target.findViewById(R.id.tv)
public static void bind(Object object) {
String className=object.getClass().getName()+"$$ViewBinder";
//類加載
try {
Class<?> clazz = Class.forName(className);
ViewBinder viewBinder = (ViewBinder) clazz.newInstance();
viewBinder.bind(object);
} catch (Exception e) {
e.printStackTrace();
}
}
}
其實這里是通過類加載就是這里的Class.forName實現(xiàn)了java文件的查找,找到了之后我們就可以操作這個Class對象,我們這里直接調(diào)用Class對象的實例化方法newInstance()去新建一個對象,這個對象是什么呢?
很簡單,我們看到這里
String className=object.getClass().getName()+"$$ViewBinder";
這是我們上面看到的那個類呀MainActivity$$ViewBinder,那我們應(yīng)該記得它實現(xiàn)了ViewBinder接口,那接口的實現(xiàn)類就可以轉(zhuǎn)成對應(yīng)的接口對象,這個有點拗口,不對想想也應(yīng)該能理解,我們安卓里面很多類似的操作,比如把點擊事件的接口返回一個接口對象,比如下面這樣
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
這個匿名內(nèi)部類不就完成了一個接口實現(xiàn)類,不同的點擊事件就是不同的接口實現(xiàn)類,結(jié)果都返回一個View.OnClickListener的接口對象
好回到我們剛剛那里,
viewBinder.bind(object);
最后我們調(diào)用了這個,啥意思?其實就相當(dāng)于
MainActivity$$ViewBinder.bind(mainActivity)
這里是不是就調(diào)用了我們之前用注解處理器和JavaPoet生成的java文件,所以這里也很清楚的可以知道ButterKinfe為啥如果我們不調(diào)用這個方法,頁面視圖綁定會空指針。
上面基本把原理講完,最后我們看看注解處理器和JavaPoet怎么生成我們需要的這個文件
public class MainActivity$$ViewBinder implements ViewBinder<MainActivity> {
@Override
public void bind(MainActivity target) {
target.tv = target.findViewById(2131165359);
}
}
我先扔一堆代碼給大家,但是不要慌,后面我會一個個分析
@AutoService(Processor.class)
@SupportedAnnotationTypes({Constants.BINDERVIEW_ANNOTATION_TYPES})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class BindViewProcessor extends AbstractProcessor {
private Elements elementUitls;
private Types typeUtils;
private Messager messager;
private Filer filer;
private Map<TypeElement, List<Element>> tempBindViewMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUitls = processingEnv.getElementUtils();
typeUtils = processingEnv.getTypeUtils();
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
messager.printMessage(Diagnostic.Kind.NOTE, "init finish,start process annotation ..........");
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
if (set.isEmpty()) return false;
Set<? extends Element> bindViewElements = roundEnv.getElementsAnnotatedWith(BindView.class);
if (!bindViewElements.isEmpty()) {
valueOfMap(bindViewElements);
try {
createJavaFile();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
return false;
}
private void createJavaFile() throws IOException {
if (tempBindViewMap.isEmpty()) return;
for (Map.Entry<TypeElement, List<Element>> entry : tempBindViewMap.entrySet()) {
//獲取當(dāng)前類名
ClassName className = ClassName.get(entry.getKey());
//生成一個名為target參數(shù) 類型就是上面的typeName
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(entry.getKey()), Constants.TARGET_PARAMETER_NAME)
.build();//target
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.BIND_METHOD_NAME)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec);
for (Element fieldElement : entry.getValue()) {
String fieldName = fieldElement.getSimpleName().toString();
//獲取BindView上面的id值
int viewId = fieldElement.getAnnotation(BindView.class).value();
String methodContent = "$N." + fieldName + " = $N.findViewById($L)";
//拼接成target.tv=target.findViewById(R.id.tv)
methodBuilder.addStatement(methodContent, Constants.TARGET_PARAMETER_NAME,
Constants.TARGET_PARAMETER_NAME, viewId);
}
//找得到所有實現(xiàn)了ViewBinder接口的類
TypeElement viewBindType = elementUitls.getTypeElement(Constants.VIEWBINDER);
// 這里需要理解一下 實現(xiàn)接口泛型 ViewBinder<MainActivity> 拼接成這個樣子
ParameterizedTypeName typeName = ParameterizedTypeName.get(ClassName.get(viewBindType),
className);
if(typeName!=null) {
JavaFile.builder(className.packageName(),
TypeSpec.classBuilder(className.simpleName() + "$$ViewBinder")
.addSuperinterface(typeName) //實現(xiàn)viewBinder接口
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuilder.build())
.build()
).build().writeTo(filer);
}
}
}
private void valueOfMap(Set<? extends Element> bindViewElements) {
for (Element element : bindViewElements) {
//注解在屬性之上,屬性節(jié)點父節(jié)點是類節(jié)點
TypeElement classElement = (TypeElement) element.getEnclosingElement();
if (tempBindViewMap.containsKey(classElement)) {
tempBindViewMap.get(classElement).add(element);
} else {
List<Element> fields = new ArrayList<>();
fields.add(element);
tempBindViewMap.put(classElement, fields);
}
}
}
}
首先我們看看這個類頭部的注解
@AutoService(Processor.class)
@SupportedAnnotationTypes({Constants.BINDERVIEW_ANNOTATION_TYPES})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
這里@AutoService(Processor.class)表示這是一個注解處理器
@SupportedAnnotationTypes({Constants.BINDERVIEW_ANNOTATION_TYPES})表示這個注解處理器需要處理的注解類型,這里這個名字必須正確,比如我這里的是
public static final String BINDERVIEW_ANNOTATION_TYPES="com.example.annotation.BindView";
錯了就出問題了,如果發(fā)現(xiàn)注解處理器不生效首先檢查一下這里
@SupportedSourceVersion(SourceVersion.RELEASE_7)
然后這里就是支持JDK7
我們再看看下面這個幾個部分
private Elements elementUitls;
private Messager messager;
private Filer filer;
private Map<TypeElement, List<Element>> tempBindViewMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUitls = processingEnv.getElementUtils();
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
messager.printMessage(Diagnostic.Kind.NOTE, "init finish,start process annotation ..........");
}
首先是這個Elements elementUitls是用來操作類元素的,具體作用我們等一下分析下面的代碼可以看到
Messager messager用于控制臺打印消息的,Filer filer用于輸出Java文件的,Map<TypeElement, List<Element>> tempBindViewMap主要用來存儲類里面的元素信息,啥意思?就是用來存儲類似
@BindView(R.id.tv)
TextView tv;
這樣子的類變量信息,后面會分析到
init方法基本就是初始化這幾個變量
下面就是核心方法,注解處理了
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
if (set.isEmpty()) return false;
Set<? extends Element> bindViewElements = roundEnv.getElementsAnnotatedWith(BindView.class);
if (!bindViewElements.isEmpty()) {
valueOfMap(bindViewElements);
try {
createJavaFile();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
return false;
}
我們通過
Set<? extends Element> bindViewElements = roundEnv.getElementsAnnotatedWith(BindView.class);
找到所有包含BindView注解的對象,然后我們看看valueOfMap做了啥
private void valueOfMap(Set<? extends Element> bindViewElements) {
for (Element element : bindViewElements) {
//注解在屬性之上,屬性節(jié)點父節(jié)點是類節(jié)點
TypeElement classElement = (TypeElement) element.getEnclosingElement();
if (tempBindViewMap.containsKey(classElement)) {
tempBindViewMap.get(classElement).add(element);
} else {
List<Element> fields = new ArrayList<>();
fields.add(element);
tempBindViewMap.put(classElement, fields);
}
}
}
這個方法其實就是通過遍歷所有的添加了BindView注解的對象,然后
TypeElement classElement = (TypeElement) element.getEnclosingElement();
找到對應(yīng)的屬于哪個類的屬性,這個 classElement就是對應(yīng)的類名,我們這里其實就是MainActivity類,然后判斷Map里面是否有該類,如果存在,就取出來然后給他的List<Element>對象添加一個element即使一個屬性對象,然后不存在,就新建一個List<Element>對象并且添加一個element然后把對應(yīng)的類和List<Element>放入Map存起來
說的很復(fù)雜哈,其實就是遍歷你類里面使用到BindView注解的屬性,然后完事
下面到了重頭戲createJavaFile()這個方法即使真正生成Java文件的地方
private void createJavaFile() throws IOException {
if (tempBindViewMap.isEmpty()) return;
for (Map.Entry<TypeElement, List<Element>> entry : tempBindViewMap.entrySet()) {
//獲取當(dāng)前類名
ClassName className = ClassName.get(entry.getKey());
//生成一個名為target參數(shù) 類型就是上面的typeName
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(entry.getKey()), Constants.TARGET_PARAMETER_NAME)
.build();//target
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.BIND_METHOD_NAME)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec);
for (Element fieldElement : entry.getValue()) {
String fieldName = fieldElement.getSimpleName().toString();
//獲取BindView上面的id值
int viewId = fieldElement.getAnnotation(BindView.class).value();
String methodContent = "$N." + fieldName + " = $N.findViewById($L)";
//拼接成target.tv=target.findViewById(R.id.tv)
methodBuilder.addStatement(methodContent, Constants.TARGET_PARAMETER_NAME,
Constants.TARGET_PARAMETER_NAME, viewId);
}
//找得到所有實現(xiàn)了ViewBinder接口的類
TypeElement viewBindType = elementUitls.getTypeElement(Constants.VIEWBINDER);
// 這里需要理解一下 實現(xiàn)接口泛型 ViewBinder<MainActivity> 拼接成這個樣子
ParameterizedTypeName typeName = ParameterizedTypeName.get(ClassName.get(viewBindType),
className);
if(typeName!=null) {
JavaFile.builder(className.packageName(),
TypeSpec.classBuilder(className.simpleName() + "$$ViewBinder")
.addSuperinterface(typeName) //實現(xiàn)viewBinder接口
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuilder.build())
.build()
).build().writeTo(filer);
}
}
}
這個方法很長,沒關(guān)系,我們從下往上看
//找得到所有實現(xiàn)了ViewBinder接口的類
TypeElement viewBindType = elementUitls.getTypeElement(Constants.VIEWBINDER);
// 這里需要理解一下 實現(xiàn)接口泛型 ViewBinder<MainActivity> 拼接成這個樣子
ParameterizedTypeName typeName = ParameterizedTypeName.get(ClassName.get(viewBindType),
className);
if(typeName!=null) {
JavaFile.builder(className.packageName(),
TypeSpec.classBuilder(className.simpleName() + "$$ViewBinder")
.addSuperinterface(typeName) //實現(xiàn)viewBinder接口
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuilder.build())
.build()
).build().writeTo(filer);
}
這里啥意思,首先這兩句代碼
TypeElement viewBindType = elementUitls.getTypeElement(Constants.VIEWBINDER);
// 這里需要理解一下 實現(xiàn)接口泛型 ViewBinder<MainActivity> 拼接成這個樣子
ParameterizedTypeName typeName = ParameterizedTypeName.get(ClassName.get(viewBindType),
className);
我們通過開頭講到的Element elementUitls這個可以操作類的對象,找到一個接口類型Constants.VIEWBINDER=public static final CharSequence VIEWBINDER = "com.example.library.ViewBinder";這里也不能錯,錯了玩完
找到這個接口之后,通過ParameterizedTypeName typeName = ParameterizedTypeName.get(ClassName.get(viewBindType), className);構(gòu)建成一個類似這樣的接口ViewBinder<MainActivity>
這里的className在開頭的這個位置我們能看到
//獲取當(dāng)前類名
ClassName className = ClassName.get(entry.getKey());
作用就是獲取當(dāng)前類名,這是一個對象哈,不單單是類名還有其他屬性
然后我們看這里
JavaFile.builder(className.packageName(),
TypeSpec.classBuilder(className.simpleName() + "$$ViewBinder")
通過className獲取包名,然后傳入類型對應(yīng)要生成的類名這里其實就是MainActivity$$ViewBinder
然后
.addSuperinterface(typeName) //實現(xiàn)viewBinder接口
實現(xiàn)ViewBinder<MainActivity>接口
然后
.addModifiers(Modifier.PUBLIC)
聲明類的訪問權(quán)限為public
然后
.addMethod(methodBuilder.build())
傳入要實現(xiàn)的方法,這個我們等一下往上看能看到,可以先預(yù)告一下,我們還記得這個類嗎
public class MainActivity$$ViewBinder implements ViewBinder<MainActivity> {
@Override
public void bind(MainActivity target) {
target.tv = target.findViewById(2131165359);
}
}
我們上面基本完成了類的外殼的搭建,結(jié)構(gòu)是這樣子的
package com.example.javapoetbutterknife;
public class MainActivity$$ViewBinder implements ViewBinder<MainActivity> {
}
有包名,然后的類的一些配置,那我們知道等一下要實現(xiàn)的就是bind方法
先把上面收尾
).build().writeTo(filer);
其實就是寫入Java文件,比如我這里的這個文件

不同版本的AS,位置不一樣,不過基本都在 這個
build文件夾,具體可以百度一下
回到我們上面的,現(xiàn)在要實現(xiàn)一個方法,先分析一下,這個方法首先需要一個參數(shù),這個參數(shù)怎么獲取,參數(shù)類型是什么,還有參數(shù)有了之后,我們要實現(xiàn)里面的findViewById方法,好,我們先看代碼
//生成一個名為target參數(shù) 類型就是上面的typeName
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(entry.getKey()), Constants.TARGET_PARAMETER_NAME)
.build();//target
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.BIND_METHOD_NAME)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec);
for (Element fieldElement : entry.getValue()) {
String fieldName = fieldElement.getSimpleName().toString();
//獲取BindView上面的id值
int viewId = fieldElement.getAnnotation(BindView.class).value();
String methodContent = "$N." + fieldName + " = $N.findViewById($L)";
//拼接成target.tv=target.findViewById(R.id.tv)
methodBuilder.addStatement(methodContent, Constants.TARGET_PARAMETER_NAME,
Constants.TARGET_PARAMETER_NAME, viewId);
}
又是一大坨,沒事,我們先看第一句話
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(entry.getKey()), Constants.TARGET_PARAMETER_NAME)
.build();//target
我們先看看這個Constants.TARGET_PARAMETER_NAME=public static final String TARGET_PARAMETER_NAME = "target";
其實就是聲明一個參數(shù)類型 ClassName.get(entry.getKey())取出來的是MainActivity,然后這個對象ParameterSpec parameterSpec其實就是類似這個(MainActivity target),記得對比上面那個方法代碼
繼續(xù)走起
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.BIND_METHOD_NAME)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(parameterSpec);
這里構(gòu)造了一個方法對象,首先老規(guī)矩看看這個 Constants.BIND_METHOD_NAME是啥
public static final String BIND_METHOD_NAME = "bind";
其實就是方法名稱bind,沒錯第一個參數(shù)傳的就是方法 名稱,然后是Override.class注解,方法訪問權(quán)限為public,參數(shù)為(MainActivity target)上面生成的
最后最后啦 ,就是里面的方法 實現(xiàn)
for (Element fieldElement : entry.getValue()) {
String fieldName = fieldElement.getSimpleName().toString();
//獲取BindView上面的id值
int viewId = fieldElement.getAnnotation(BindView.class).value();
String methodContent = "$N." + fieldName + " = $N.findViewById($L)";
//拼接成target.tv=target.findViewById(R.id.tv)
methodBuilder.addStatement(methodContent, Constants.TARGET_PARAMETER_NAME,
Constants.TARGET_PARAMETER_NAME, viewId);
}
首先 解釋一下這個循環(huán),因為一個類里面一般都不止一個控件需要綁定把,我們這里是demo所以我只寫了一個TextView tv ,然后
String fieldName = fieldElement.getSimpleName().toString();
獲取控件名稱,比如這里fieldName ==tv
然后獲取注解的ID值
//獲取BindView上面的id值
int viewId = fieldElement.getAnnotation(BindView.class).value();
我們demo就是R.id.tv的整型值
下面有個比較需要稍微了解JavaPoet的東西
String methodContent = "$N." + fieldName + " = $N.findViewById($L)";
$N for Name就是名字 $L for Link就是引用 這里 $N就是target
$L 即使R.id.tv的整型值
最后
//拼接成target.tv=target.findViewById(R.id.tv)
methodBuilder.addStatement(methodContent, Constants.TARGET_PARAMETER_NAME,
Constants.TARGET_PARAMETER_NAME, viewId);
拼接成target.tv=target.findViewById(R.id.tv)
Ok 全劇終
大家想了解更多直接去我的github看代碼實現(xiàn)