Demo的源碼地址在 mini-butterknife, 有興趣的可以下載源碼來看.
Butterknife 框架介紹
butterknife是一款View注入的框架,在android開發(fā)時省去我們重復(fù)的敲打findViewById
,setOnclickListener
等方法.
Field and method binding for Android views which uses annotation processing to generate boilerplate code for you.
通過注解處理器 (Annotation Processor) 自動生成樣板代碼,來綁定 Android視圖中的 字段 和方法.
從方法的介紹中,我們可以看出, Butterknife 使用 APT(Annotation Processing Tool)編譯時解析技術(shù)來實現(xiàn)代碼的生成。
(注:我們也可以在 運行時, 通過反射的方式拿到 注解信息進行處理, 但是這種方式效率比較低,不是本文討論的問題.)
而代碼的生成, Butterknife 則使用 JavaPoet
庫來生成java文件.
APT
是需要你 繼承 AbstractProcessor
類, 在編譯期的時候會運行 AbstractProcessor
的 process
方法來解析注解, 以及處理生成樣板代碼.
而樣板代碼,則通過 反射調(diào)用來進行.
所以, 可以總結(jié)成三個步驟
- 繼承
AbstractProcessor
解析注解數(shù)據(jù) - 根據(jù)解析的注解數(shù)據(jù),通過
JavaPoet
生成java樣板代碼 - 運行時,通過反射獲取到樣板代碼的實例,進行調(diào)用
注解知識
網(wǎng)上有很多java注解相關(guān)的文章, 這里就不對注解的基礎(chǔ)知識進行展開,推薦 Java注解處理器 進行閱讀.
這里主要對自定義注解的解析做一些說明.
AbstractProcessor
需要關(guān)注注解處理器的以下方法 :
void init(ProcessingEnvironment processingEnv)
init
方法中,主要是為了 獲取以下 輔助的方法類.
如 env.getFiler()
: 注解的文件處理類
env.getMessager()
: 日志輸出類
env.getElementUtils()
: 元素輔助類
env.getTypeUtils()
: 類型輔助類等.
Set<String> getSupportedAnnotationTypes()
這里需要你指定 你關(guān)注的注解. 或者說 你關(guān)注的注解類型,需要在這里注冊,process
方法才能夠正常處理你關(guān)注的注解.
SourceVersion getSupportedSourceVersion()
指定你使用的Java版本。通常這里返回SourceVersion.latestSupported().
boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
這里是你的掃描峦萎、評估和處理注解,以及生成Java文件的邏輯代碼.
模板代碼:
// 使用@AutoService注解,避免了在resource文件夾中注冊的步驟
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
private Filer filer;
private Messager messager;
/**
* 初始化, 獲取各種工具類
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
}
/**
* 注冊感興趣的注解類
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(BindView.class.getCanonicalName());
annotations.add(OnClick.class.getCanonicalName());
return annotations;
}
/**
* 返回使用的java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 掃描判族、評估和處理注解瓜喇,以及生成Java文件的邏輯代碼
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return handleProcess(annotations, roundEnv);
}
}
MODEL API
注解元素解析相關(guān)的api,稱其為MODEL API
是因為 注解解析的類存放在 javax.lang.model
包下.
在process
方法中,我們通過 for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class))
來遍歷我們感興趣的注解元素.
- 判斷注解是否使用在字段上
// 判斷注釋 是否在 字段上
if (!(element instanceof VariableElement) || element.getKind() != ElementKind.FIELD) {
throw new IllegalStateException("@BindView annotation must be on a field.");
}
- 判斷注解是否使用在方法上
// 判斷注釋 是否在 方法上
if (!(element instanceof ExecutableElement) || element.getKind() != ElementKind.METHOD) {
throw new IllegalStateException("@OnClick annotation must be on a method.");
}
- 獲取被注解所在的類的名稱
// 獲取目標(biāo)類的類型
// 如(xxx.MainActivity)
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
String targetClassName = typeElement.getQualifiedName().toString();
- 獲取注解實例,以及注解中的值
// 獲取注解實例
BindView bindView = element.getAnnotation(BindView.class);
// 獲取注解值
// @BindView(R.id.btn) 中 R.id.btn對應(yīng)的int值
int value = bindView.value();
- 獲取字段或者方法的名稱
// 獲取字段或者方法的名稱
// Button btn; 中的 btn
// void handleClick(View v); 中的 handleClick
String name = element.getSimpleName().toString();
- 獲取字段或者方法中的參數(shù)或者返回值的類型
由于原生中,確定類型的判斷方法比較繁瑣(需要根據(jù)各種getKind判斷基礎(chǔ)類型,引用類型,數(shù)組類型,泛型等),這里可以直接通過 JavaPoet
庫中的 TypeName
類來確定類型
// 可以根據(jù) javapoet 中的 TypeName 來確定類型
// @BindView(R.id.btn) Button btn; 中 得到 android.view.Button
TypeMirror mirror = element.asType();
TypeName typeName = TypeName.get(mirror);
- 獲取方法的返回類型
// 獲取返回值的類型
ExecutableElement executableElement = (ExecutableElement) element;
TypeMirror returnType = executableElement.getReturnType();
TypeName returnClass = TypeName.get(returnType);
- 獲取方法的參數(shù)信息
ExecutableElement executableElement = (ExecutableElement) element;
// 獲取方法的參數(shù)信息
List<? extends VariableElement> parameters = executableElement.getParameters();
// 獲取傳入?yún)?shù)的個數(shù)
int parameterSize = parameters.size();
// 獲取參數(shù)類型以及參數(shù)名稱
for (VariableElement parameter : parameters) {
TypeMirror paramType = parameter.asType();
// 參數(shù)類型
TypeName paramClass = TypeName.get(paramType);
// 參數(shù)名
Name paramName = parameter.getSimpleName();
}
- 判斷是否 該類型是否是某個類型的子類型
/**
* 遞歸向上查找 是否是其對應(yīng)的子類型
*/
static boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) {
if (otherType.equals(typeMirror.toString())) {
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;
}
TypeElement typeElement = (TypeElement) element;
TypeMirror superType = typeElement.getSuperclass();
if (isSubtypeOfType(superType, otherType)) {
return true;
}
for (TypeMirror interfaceType : typeElement.getInterfaces()) {
if (isSubtypeOfType(interfaceType, otherType)) {
return true;
}
}
return false;
}
JavaPoet
JavaPoet
是一個使用可編程的方式來生成java源文件.
GitHub地址 : JavaPoet
readme 里面詳細的介紹了 它的用法,這里就不再贅述.
動手編寫B(tài)utterKnife
一個示例
public class MainActivity extends AppCompatActivity {
// 解綁器
private Unbinder unbinder;
@BindView(R.id.btn2)
Button btn2;
@BindView(R.id.btn)
Button button;
int id = 0;
@OnClick({R.id.btn, R.id.btn2})
void clickMethod(View view) {
Toast.makeText(this, "i am a toast !!", Toast.LENGTH_SHORT).show();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(view -> {
button.setText("i'm button " + (++id));
btn2.setText("xxx button " + (++id));
});
// 綁定
unbinder = ButterKnife.bind(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 解綁, 移除監(jiān)聽器等
if (unbinder != null) unbinder.unbind();
}
}
這里再說一下 ButterKnife的流程.
- 通過 注解處理器 解析注解數(shù)據(jù), 并且生成 java源代碼
public class MainActivity_ViewBinding implements Unbinder {
private MainActivity target;
private View view7f080025;
private View view7f080024;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.btn2, "field 'btn2' and method 'clickMethod'");
target.btn2 = Utils.castView(view, R.id.btn2, "field 'btn2'", Button.class);
view7f080025 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.clickMethod(p0);
}
});
view = Utils.findRequiredView(source, R.id.btn, "field 'button' and method 'clickMethod'");
target.button = Utils.castView(view, R.id.btn, "field 'button'", Button.class);
view7f080024 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.clickMethod(p0);
}
});
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.btn2 = null;
target.button = null;
view7f080025.setOnClickListener(null);
view7f080025 = null;
view7f080024.setOnClickListener(null);
view7f080024 = null;
}
}
這個就是 ButterKnife 編譯時生成代碼.
- 在
ButterKnife.bind(this);
時, 方法內(nèi)部使用反射生成上述樣板代碼的實例返回.
也就是我們拿到的 Unbinder
實例對象.
有了上面的介紹,我們可以很輕松的寫出上述的代碼功能.
模塊劃分
我們將 功能分成3個模塊,來構(gòu)建項目.
- 注解模塊(butterknife_annotations)
注解模塊, 存放著所有的注解類,之所以將它 作為一個單獨的模塊存放,是因為 注解類在注解處理器階段(即編譯階段)需要使用到, 并且在運行階段(第三方使用過程中) 也需要用到, 這種兩個階段都需要用到的我們單獨分一個模塊.
- 注解處理器模塊(butterknife_processor)
注解處理器模塊,使用來 獲取注解信息和生成java樣板代碼的. 只在編譯器使用, 代碼不會被打包到程序中去.
gradle
依賴中使用 annotationProcessor project(':butterknife_processor')
來導(dǎo)入.
- butterknife模塊
這個模塊負責(zé)綁定 activity
等類, 通過反射獲取 樣板代碼的實例,返回給調(diào)用者,這個模塊是 運行時依賴.
注解模塊(butterknife_annotations)
文章的目的是為了 實現(xiàn) @BindView
和 @OnClick
注解的功能.
因為, 這個模塊,只有這兩個類的代碼.
// OnClick.java
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface OnClick {
@IdRes int[] value();
}
// BindView.java
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
@IdRes int value();
}
這里由于不考慮,運行時通過反射拿到注解, 所以直接使用 RetentionPolicy.SOURCE
策略.
注解處理器模塊(butterknife_processor)
這個模塊是生成樣板代碼的模塊, 我們需要每一個 目標(biāo)類(如MainActivity)中的所有注解, 解析后生成一份樣板代碼.
@BindView(R.id.btn2)
Button btn2;
@BindView
是注解在 字段上, 我們需要關(guān)注它 字段名, 和字段類型, 以及注解的值(R.id.btn2)
public class FieldBinding {
private int value;
private ClassName fieldType;
private String fieldName;
public FieldBinding(int value, ClassName fieldType, String name) {
this.value = value;
this.fieldType = fieldType;
this.fieldName = name;
}
public int getValue() {
return value;
}
public ClassName getFieldType() {
return fieldType;
}
public String getFieldName() {
return fieldName;
}
}
@OnClick({R.id.btn, R.id.btn2})
void click(View view) {
Toast.makeText(this, "i am a toast !!", Toast.LENGTH_SHORT).show();
}
@OnClick
是注解在方法上的, 我們需要關(guān)注它的 方法名,和注解的值; 由于 @OnClick
注解的方法,如果有方法參數(shù),必定只能是 android.view.View
,所以這里只判斷是否有參數(shù)就行了.
public class MethodBinding {
private String methodName;
private int value;
private boolean hasParam;
public MethodBinding(String methodName, int value, boolean hasParam) {
this.methodName = methodName;
this.hasParam = hasParam;
this.value = value;
}
public String getMethodName() {
return methodName;
}
public int getValue() {
return value;
}
public boolean hasParam() {
return hasParam;
}
}
我們需要根據(jù)我們關(guān)注的這些數(shù)據(jù), 使用 javapoet
庫來生成樣板代碼, 這里直接貼出, 生成代碼的類, api的使用 進入 javapoet
官網(wǎng)自行查找.
class ViewBinding {
private static final ClassName UTILS = ClassName.get("cn.jimmie.learn.butterknife", "Utils");
private static final ClassName VIEW = ClassName.get("android.view", "View");
private static final ClassName UNBINDER = ClassName.get("cn.jimmie.learn.butterknife", "Unbinder");
private static final ClassName LISTENER = ClassName.get("cn.jimmie.learn.butterknife", "DebouncingOnClickListener");
private static final ClassName ILLEGAL_STATE_EXCEPTION = ClassName.get("java.lang", "IllegalStateException");
private List<FieldBinding> fieldBindings = new ArrayList<>();
private List<MethodBinding> methodBindings = new ArrayList<>();
private ClassName target;
ViewBinding(ClassName target) {
this.target = target;
}
void addFiled(FieldBinding field) {
fieldBindings.add(field);
}
void addMethod(MethodBinding method) {
methodBindings.add(method);
}
JavaFile brewJava() {
// 單參數(shù)構(gòu)造函數(shù) $ctor(Object target)
MethodSpec ctor1 = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(target, "target")
.addStatement("this(target, target.getWindow().getDecorView())")
.build();
// 構(gòu)建兩個參數(shù)的構(gòu)造函數(shù) $ctor(Object target,View source)
MethodSpec.Builder ctorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(target, "target", Modifier.FINAL)
.addParameter(VIEW, "source")
.addStatement("this.$N = $N", "target", "target");
// 解綁器
MethodSpec.Builder unbind = MethodSpec.methodBuilder("unbind")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addStatement("if (target == null) throw new $T(\"Bindings already cleared.\")", ILLEGAL_STATE_EXCEPTION);
// 類構(gòu)建器
TypeSpec.Builder clsBuilder = TypeSpec.classBuilder(target.simpleName() + "Unbinder")
.addSuperinterface(UNBINDER)
.addModifiers(Modifier.PUBLIC)
.addField(target, "target", Modifier.PRIVATE)
.addMethod(ctor1);
// 方法和字段綁定
List<Pair<FieldBinding, MethodBinding>> fieldMethods = merge();
for (Pair<FieldBinding, MethodBinding> fieldMethod : fieldMethods) {
if (fieldMethod == null) continue;
FieldBinding field = fieldMethod.getKey();
MethodBinding method = fieldMethod.getValue();
// 只綁定字段
if (field != null && method == null)
brewOnlyField(field, ctorBuilder, unbind);
// 只綁定方法
else if (field == null && method != null)
brewOnlyMethod(method, ctorBuilder, clsBuilder, unbind);
// 綁定 字段和方法
else brewFieldMethod(field, method, ctorBuilder, unbind);
}
unbind.addStatement("target = null");
clsBuilder.addMethod(ctorBuilder.build())
.addMethod(unbind.build());
return JavaFile.builder(target.packageName(), clsBuilder.build())
.build();
}
private void brewOnlyField(FieldBinding field, MethodSpec.Builder ctorBuilder, MethodSpec.Builder unbind) {
ClassName fieldType = field.getFieldType();
ctorBuilder.addStatement("target.$L = $T.findRequiredViewAsType(source, $L, \"field '$L'\", $T.class)",
field.getFieldName(), UTILS, field.getValue(), field.getFieldName(), fieldType);
unbind.addStatement("target.$L = null", field.getFieldName());
}
private void brewOnlyMethod(MethodBinding method, MethodSpec.Builder ctorBuilder, TypeSpec.Builder clsBuilder, MethodSpec.Builder unbind) {
String methodName = method.getMethodName();
int value = method.getValue();
String member = "view" + value;
TypeSpec anonymous = brewAnonymousClass(methodName, method.hasParam());
// 添加成員變量
clsBuilder.addField(VIEW, member, Modifier.PRIVATE);
// view7f080024 = Utils.findRequiredView(source, R.id.btn, "method 'click'");
ctorBuilder.addStatement("$L = $T.findRequiredView(source, $L, \"method 'click'\")",
member, UTILS, value);
ctorBuilder.addStatement("$L.setOnClickListener($L)", member, anonymous);
unbind.addStatement("$L.setOnClickListener(null)", member);
unbind.addStatement("$L = null", member);
}
private void brewFieldMethod(FieldBinding field, MethodBinding method, MethodSpec.Builder ctorBuilder, MethodSpec.Builder unbind) {
ClassName fieldType = field.getFieldType();
String methodName = method.getMethodName();
ctorBuilder.addStatement("target.$L = $T.findRequiredViewAsType(source, $L, \"field '$L' and method '$L'\", $T.class)",
field.getFieldName(), UTILS, field.getValue(), field.getFieldName(), methodName, fieldType);
TypeSpec anonymous = brewAnonymousClass(methodName, method.hasParam());
// target.view.setOnClickListener(new DebouncingOnClickListener() {
ctorBuilder.addStatement("target.$L.setOnClickListener($L)", field.getFieldName(), anonymous);
unbind.addStatement("target.$L.setOnClickListener(null)", field.getFieldName());
unbind.addStatement("target.$L = null", field.getFieldName());
}
private TypeSpec brewAnonymousClass(String name, boolean hasParam) {
String statement = hasParam ? "target.$L(v)" : "target.$L()";
return TypeSpec.anonymousClassBuilder("")
.addSuperinterface(LISTENER)
.addMethod(MethodSpec.methodBuilder("doClick")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(VIEW, "v")
.addStatement(statement, name)
.build())
.build();
}
// 合并 字段綁定 和 方法綁定相關(guān)
private List<Pair<FieldBinding, MethodBinding>> merge() {
List<Pair<FieldBinding, MethodBinding>> list = new ArrayList<>();
boolean hasAdd = false;
for (FieldBinding fieldBinding : fieldBindings) {
for (MethodBinding methodBinding : methodBindings) {
if (fieldBinding.getValue() == methodBinding.getValue()) {
list.add(new Pair<>(fieldBinding, methodBinding));
hasAdd = true;
break;
}
}
if (!hasAdd) list.add(new Pair<>(fieldBinding, null));
else hasAdd = false;
}
for (MethodBinding methodBinding : methodBindings) {
for (FieldBinding fieldBinding : fieldBindings) {
if (fieldBinding.getValue() == methodBinding.getValue()) {
hasAdd = true;
break;
}
}
if (!hasAdd) list.add(new Pair<>(null, methodBinding));
else hasAdd = false;
}
return list;
}
}
接下來,我們來解析 注解數(shù)據(jù),一下的代碼出現(xiàn)在
先對 @BindView
的注解進行解析 class ButterKnifeProcessor extends AbstractProcessor
的 process
方法中.
// 綁定ViewId 遍歷所有被注解了@BindView的元素
for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
// 判斷注釋 是否在 字段上
if (!(element instanceof VariableElement) || element.getKind() != ElementKind.FIELD) {
throw new IllegalStateException("@BindView annotation must be on a field.");
}
// 獲取目標(biāo)類的類型 如(xxx.MainActivity)
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
String targetClassName = typeElement.getQualifiedName().toString();
// 根據(jù)類名來生成文件, 一個被注解的類中所有的注解對應(yīng)生成一份類文件
ViewBinding binding = bindingMap.get(targetClassName);
if (binding == null) {
binding = new ViewBinding(ClassName.bestGuess(targetClassName));
bindingMap.put(targetClassName, binding);
}
// 獲取注解上的值(這里是R.id對應(yīng)的int值)
int value = element.getAnnotation(BindView.class).value();
// 獲取字段的類型
ClassName fieldClass = (ClassName) ClassName.get(element.asType());
// 判斷字段類型是否是View的子類型
if (!Utils.isSubtypeOfType(element.asType(), "android.view.View")) {
throw new IllegalStateException("field type must be the child of android.view.View");
}
// 獲取字段的名稱
String name = element.getSimpleName().toString();
// 添加字段綁定
FieldBinding field = new FieldBinding(value, fieldClass, name);
binding.addFiled(field);
shouldWrite = true;
}
然后對 @OnClick
注解進行解析
// 綁定監(jiān)聽事件 遍歷所有被注解了@OnClick的元素
for (Element element : roundEnv.getElementsAnnotatedWith(OnClick.class)) {
// 判斷注釋 是否在 方法上
if (!(element instanceof ExecutableElement) || element.getKind() != ElementKind.METHOD) {
throw new IllegalStateException("@Click annotation must be on a method.");
}
ExecutableElement executableElement = (ExecutableElement) element;
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
// 獲取目標(biāo)類的類型 如(xxx.MainActivity)
String targetName = typeElement.getQualifiedName().toString();
// 添加方法綁定
ViewBinding binding = bindingMap.get(targetName);
if (binding == null) {
binding = new ViewBinding(ClassName.bestGuess(targetName));
bindingMap.put(targetName, binding);
}
// 獲取方法的參數(shù)列表
List<? extends VariableElement> methodParameters = executableElement.getParameters();
// 獲取方法參數(shù)的個數(shù)
int methodParameterSize = methodParameters.size();
// 參數(shù)校驗
if (methodParameterSize > 1) {
throw new IllegalStateException("@Click method only has one param, and must be android.view.View");
} else if (methodParameterSize == 1) {
VariableElement param = methodParameters.get(0);
TypeName paramType = TypeName.get(param.asType());
if (!paramType.toString().equals("android.view.View")) {
throw new IllegalStateException("@Click method only has one param, and must be android.view.View");
}
}
// 獲取返回類型
TypeName returnType = TypeName.get(executableElement.getReturnType());
if (!returnType.toString().equals("void")) {
throw new IllegalStateException("@Click method return type must be void");
}
// 獲取注解上的值 [R.id.x1,R.id.x2]
int[] values = element.getAnnotation(OnClick.class).value();
if (values.length == 0) continue;
// 獲取注解上的方法名
String methodName = element.getSimpleName().toString();
// 添加 方法綁定
for (int value : values) {
MethodBinding method = new MethodBinding(methodName, value, methodParameterSize == 1);
binding.addMethod(method);
}
shouldWrite = true;
}
最后對解析的數(shù)據(jù)生成 樣板代碼.
if (shouldWrite) {
// 生成java文件
for (String key : bindingMap.keySet()) {
ViewBinding binding = bindingMap.get(key);
JavaFile javaFile = binding.brewJava();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
這樣我們的 注解解析和生成樣板代碼的過程就結(jié)束.
我們來驗證下, 在 MainActivity中,聲明一下字段和方法.
@BindView(R.id.btn2)
Button btn2;
@BindView(R.id.btn)
Button button;
int id = 0;
@OnClick({R.id.btn, R.id.btn2})
void click(View view) {
Toast.makeText(this, "i am a toast !!", Toast.LENGTH_SHORT).show();
}
然后, 運行 gradle
的 compileDebugJavaWithJavac
腳本, 就會自動生成一份 MainActivityUnbinder
的樣板文件.
public class MainActivityUnbinder implements Unbinder {
private MainActivity target;
public MainActivityUnbinder(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
public MainActivityUnbinder(final MainActivity target, View source) {
this.target = target;
target.btn2 = Utils.findRequiredViewAsType(source, 2131230757, "field 'btn2' and method 'click'", Button.class);
target.btn2.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View v) {
target.click(v);
}
});
target.button = Utils.findRequiredViewAsType(source, 2131230756, "field 'button' and method 'click'", Button.class);
target.button.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View v) {
target.click(v);
}
});
}
@Override
public void unbind() {
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.btn2.setOnClickListener(null);
target.btn2 = null;
target.button.setOnClickListener(null);
target.button = null;
target = null;
}
}
butterknife模塊
這個模塊是用于反射調(diào)用 樣板代碼的.
public class ButterKnife {
private static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
@UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
@UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
try {
return constructor.newInstance(target, source);
} catch (Exception e) {
throw new RuntimeException("Unable to create binding instance.", e.getCause());
}
}
@UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null) {
return bindingCtor;
}
String clsName = cls.getName();
try {
Class<?> bindingClass = Objects.requireNonNull(cls.getClassLoader()).loadClass(clsName + "Unbinder");
//noinspection unchecked
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} catch (Exception e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
}
這種代碼,比較容易理解, 就不做過多說明, 這里說明下 其他兩個類.DebouncingOnClickListener
和 Unbinder
.
DebouncingOnClickListener
類的作用是,避免在極短的時間內(nèi)重復(fù)的過快的響應(yīng)點擊事件.
public abstract class DebouncingOnClickListener implements View.OnClickListener {
static boolean enabled = true;
private static final Runnable ENABLE_AGAIN = () -> enabled = true;
@Override public final void onClick(View v) {
if (enabled) {
enabled = false;
v.post(ENABLE_AGAIN);
doClick(v);
}
}
public abstract void doClick(View v);
}
Unbinder
接口是 樣板代碼 都要實現(xiàn)的接口, 用于監(jiān)聽事件 和 資源的移除.
public interface Unbinder {
Unbinder EMPTY = () -> {};
void unbind();
}
至此, 實現(xiàn) ButterKnife
的 @BindView
和 @OnClick
功能, 都以實現(xiàn).
感興趣的同學(xué), 可以到 mini-butterknife,下載源碼查看