一個星期沒更新博客了,雖然目前博客很亂!最近比較忙,視力有些下降,不過ButterKnife的事件實現終算完成了!
新博客地址
ButterKnifeDemo實現(註解完善,方便閱讀)
目標
之所以butterknife可以實現點擊view的時候調用註解過的方法,其實是在點擊的回調方法中調用目標類的相應註釋過的方法:
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View v) {
//HomeActivity.click(View v)
target.click(v);
}
});
拿我的demo爲例,我們最終是要生成一個如下的類:
這裏寫代碼片
package com.turbo.demo.apt;
import android.view.View;
import com.turbo.apt.library.internal.DebouncingOnClickListener;
import com.turbo.apt.library.internal.Finder;
import com.turbo.apt.library.internal.ViewBinder;
/**
* Created by LuckyTurbo on 16/4/11.
*
* 待生成的類
*/
public class MainActivity_ViewBinder<T extends MainActivity> implements ViewBinder<T> {
@Override
public void bind(Finder finder, final T target, Object source) {
View view = null;
view = finder.findRequiredView(source,R.id.rightView,"rightView 我 啊");
target.rightView = finder.castView(view,R.id.rightView,"rightView 我 啊");
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View v) {
target.click(v);
}
});
}
}
那麼下面要做的就是收集各種註解數據,然後再根據這些數據生成最終的類。
收集數據
絕大部分方法的數據都是從註解中獲取的,那麼我們如何設計註解呢?
註解設計分析
需求:
1.id
2.設置方法 (example:setOnLongClickListener)
3.接口類型 (android.view.View.OnLongClickListener)
4.接口中的方法:
1.方法名 (onLongClick)
2.參數類型 (android.view.View)
3.方法返回值 (boolean)
這麼多東西如果在每個註解裏面都加上,想想都噁心,但是註解並沒有繼承這一說,但是我們可以在註解的基礎上再進行註解,上層註解裏面可以設定默認值,每次給上層註解填值就可以了
——其實我們想要的就是規範,就是模版,其實就是針對規範編程
下面看下主要的幾個註解:
- @OnClick
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@ListenerClass(
targetType = "android.view.View",
setter = "setOnClickListener",
// 這個是butterKnife中的防卡點擊
type = "com.turbo.apt.library.internal.DebouncingOnClickListener",
method = @ListenerMethod(
name = "doClick",
parameters = "android.view.View",
returnType = "void"
)
)
public @interface OnClick {
int[] value() default {View.NO_ID};
}
- @ListenerClass
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)// 靠,獲取註解的註解好像必需要使用runtime,不然取不到
public @interface ListenerClass {
// @ListenerClass(
// targetType = "android.view.View",
// setter = "setOnClickListener",
// type = "butterknife.internal.DebouncingOnClickListener",
// method = @ListenerMethod(
// name = "doClick",
// parameters = "android.view.View"
// )
// )
// 某view
String targetType();
// 設置方法的名稱
String setter();
// 接口全稱
String type();
/** Enum which declares the listener callback methods. Mutually exclusive to {@link #method()}. */
// 跟method互斥
Class<? extends Enum<?>> callbacks() default NONE.class;
/**
* Method data for single-method listener callbacks. Mutually exclusive with {@link #callbacks()}
* and an error to specify more than one value.
*/
ListenerMethod method();
// callback的默認值
enum NONE {}
}
- @ListenerMethod
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ListenerMethod {
// listener 方法 的 名字
String name();
// 方法參數
String[] parameters() default {};
// 方法返回類型
String returnType() default "void";
/** If {@link #returnType()} is not {@code void} this value is returned when no binding exists. */
// 如果returnType 不是void,就返回這個值
String defaultReturn() default "null";
}
註解收集數據結構
註解收集數據邏輯
private Map<TypeElement, VBinderBuilder> findAndParseTargets(RoundEnvironment roundEnv) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Map<TypeElement, VBinderBuilder> vBinderBuilderMap = new LinkedHashMap<>();
// 解析所有的@Bind 元素
for (Element element : roundEnv.getElementsAnnotatedWith(Bind.class)) {
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
VBinderBuilder builderClass = null;
if (vBinderBuilderMap.containsKey(typeElement)) {
builderClass = vBinderBuilderMap.get(typeElement);
} else {
String targetType = typeElement.getQualifiedName().toString();
String classPackage = getPackageName(typeElement);
// packageName$className_ViewBinder
String className = getClassName(typeElement, classPackage) + APTConfig.SUFFIX;
builderClass = new VBinderBuilder(classPackage, className, targetType);
vBinderBuilderMap.put(typeElement, builderClass);
}
int value = element.getAnnotation(Bind.class).value();
String elementName = element.getSimpleName().toString();
TypeName typeName = TypeName.get(element.asType());
FieldViewBinding fieldViewBinding = new FieldViewBinding(elementName, typeName, value);
builderClass.addField(value, fieldViewBinding);
}
// 解析所有LISTENERS:方法上的事件註解
for (Class<? extends Annotation> annotationClass : LISTENERS) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotationClass)) {
// 方法元素
ExecutableElement executableElement = (ExecutableElement) element;
// 類元素(持有者元素)
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// 方法的註解對象
Annotation annotation = element.getAnnotation(annotationClass);
// 註解的value方法
Method annotationValue = annotationClass.getDeclaredMethod("value");
// 獲取註解的ids
int[] ids = (int[]) annotationValue.invoke(annotation);
// 方法名稱
String name = executableElement.getSimpleName().toString();
// 註解的註解
ListenerClass listenerClass = annotationClass.getAnnotation(ListenerClass.class);
// 註解註解的參數
ListenerMethod listenerMethod = listenerClass.method();
// 方法參數
List<? extends VariableElement> methodParameters = executableElement.getParameters();
Parameter[] parameters = Parameter.NONE;
// 真實方法參數
if (!methodParameters.isEmpty()) {
parameters = new Parameter[methodParameters.size()];
// 註解的註解中的參數類型
String[] parameterTypes = listenerMethod.parameters();
for (int i = 0; i < methodParameters.size(); i++) {
VariableElement methodParameter = methodParameters.get(i);
// 方法參數
TypeMirror methodParameterType = methodParameter.asType();
if (methodParameterType instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) methodParameterType;
methodParameterType = typeVariable.getUpperBound();
messager.printMessage(Diagnostic.Kind.ERROR, ((TypeVariable) methodParameterType).asElement().getSimpleName());
}
for (int j = 0; j < parameterTypes.length; j++) {
// 封裝了真實參數的位置,類型
parameters[j] = new Parameter(j, TypeName.get(methodParameterType));
}
}
}
// name:方法名 parameters:方法參數parameter required:方法上是否有Optional註解(一般不會有,所以爲true)
// 一般都是true,我們這裏沒有給註解設置Optional註解
MethodViewBinding methodViewBinding = new MethodViewBinding(name, Arrays.asList(parameters), true);
VBinderBuilder builderClass = getOrCreateTargetClass(vBinderBuilderMap, enclosingElement);
for (int id : ids){
builderClass.addMethod(id,listenerClass,listenerMethod,methodViewBinding);
}
}
}
return vBinderBuilderMap;
}
ViewBinder綁定幫助類生成
根據上一篇的實現步驟,我們生成一個實現ViewBinder接口,及其方法,實現控件初始化的類已經不是一件難事,現在最大的困難就是如何通過JavaPoet實現類似下面的代碼:
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View v) {
target.click(v);
}
});
根據butterKnife代碼我給出具體的解決步驟:
以@OnLongClick爲例:
// new DebouncingOnClickListener()
// 這裏就是一個空接口以及一個父類
TypeSpec.Builder interfaceEventBuilder = TypeSpec.anonymousClassBuilder("")
.superclass(ClassName.bestGuess(keyListenerClass.type()));
// 這裏代表的是 方法:public void doClick
MethodSpec.Builder interfaceMethodBuilder = MethodSpec.methodBuilder(method.name())
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(bestGuess(method.returnType()));
// 第一個參數 是參數類型 第一個參數是參數名(p0 p1 p2 ...)
// 方法中的參數(View v)
interfaceMethodBuilder.addParameter(bestGuess(parameterTypes[i]), "p" + i);
// 調用target的對應方法:target.click(v);
codeBuilder.add("target.$L(", methodViewBinding.getName());
整個方法的生成代碼如下:
private void addMethodBindings(MethodSpec.Builder resultBuilder, VBinderData vbinderData) {
Map<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> listenerMethodBindings = vbinderData.getMethodBindings();
if (listenerMethodBindings.isEmpty()) {
return;
}
Set<Map.Entry<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>>> entries = listenerMethodBindings.entrySet();
for (Map.Entry<ListenerClass, Map<ListenerMethod, Set<MethodViewBinding>>> entry : entries) {
ListenerClass keyListenerClass = entry.getKey();
Map<ListenerMethod, Set<MethodViewBinding>> methodValue = entry.getValue();
// 創建接口實體類(空匿名類的接口父類)
// anonymous 匿名
TypeSpec.Builder interfaceEventBuilder = TypeSpec.anonymousClassBuilder("")
.superclass(ClassName.bestGuess(keyListenerClass.type()));
// 根據ListenerClass 中的 參數 生成代碼
ListenerMethod method = keyListenerClass.method();
if (method != null) {
MethodSpec.Builder interfaceMethodBuilder = MethodSpec.methodBuilder(method.name())
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(bestGuess(method.returnType()));
String[] parameterTypes = method.parameters();
for (int i = 0; i < parameterTypes.length; i++) {
// 第一個參數 是參數類型 第一個參數是參數名(p0 p1 p2 ...)
interfaceMethodBuilder.addParameter(bestGuess(parameterTypes[i]), "p" + i);
}
boolean hasReturnType = !"void".equals(method.returnType());
// 代碼塊
CodeBlock.Builder codeBuilder = CodeBlock.builder();
if (hasReturnType) {
codeBuilder.add("return ");
}
// 一個id對應多個ListenerMethod
if (methodValue.containsKey(method)) {
for (MethodViewBinding methodViewBinding : methodValue.get(method)) {
// 調用target的對應方法
codeBuilder.add("target.$L(", methodViewBinding.getName());
List<Parameter> parameters = methodViewBinding.getParameters();
// MethodListener註解的參數
String[] methodParameters = method.parameters();
// 優化了每次parameters.size()
for (int i = 0, count = parameters.size(); i < count; i++) {
if (i > 0) {
codeBuilder.add(", ");
}
Parameter parameter = parameters.get(i);
int listenerPosition = parameter.getListenerPosition();
// 類型不一樣,範型萬能轉換
if (parameter.requiresCast(methodParameters[i])) {
codeBuilder.add("finder.<$T>castParam(p$L, $S, $L, $S, $L)\n", parameter.getType(),
listenerPosition, method.name(), listenerPosition, methodViewBinding.getName(), i);
} else {
codeBuilder.add("p$L", listenerPosition);
}
}
codeBuilder.add(");\n");
}
} else if (hasReturnType) {
codeBuilder.add("$L;\n", method.defaultReturn());
}
interfaceMethodBuilder.addCode(codeBuilder.build());
interfaceEventBuilder.addMethod(interfaceMethodBuilder.build());
}
// 如果不是view類型,需要強轉
if (!APTConfig.VIEW_TYPE.equals(keyListenerClass.targetType())) {
// targetType不是view的時候需要強轉
resultBuilder.addStatement("(($T) view).$L($L)", bestGuess(keyListenerClass.targetType()),
keyListenerClass.setter(), interfaceEventBuilder.build());
} else {
// view.setOnClickListener(對象)
resultBuilder.addStatement("view.$L($L)", keyListenerClass.setter(), interfaceEventBuilder.build());
}
}
}
其他的具體步驟大家看下demo
總結
代碼這個東西,看跟敲是兩種體驗,也是兩種結果,以後會繼續堅持,總結下從butterKnife框架細節中我學到的知識:
- BitSet : java中提供的此神器用來記錄我對一組數據的改動與否
- @Retention(RUNTIME) :註解的註解是需要runtime的,即便僅在編譯期讀取,RetentionPolicy.CLASS也是不奏效的,必需是RetentionPolicy.RUNTIME
- for (int i = 0, count = parameters.size(); i < count; i++) : 省去每次遍歷調用parameters.size()的同時,又優雅的初始化了count並附上了想要的值(智慧而優雅)