protected T target; } //比如在adapter的viewholder中 public final class SimpleAdapter$ViewHolder_ViewBinding implements Unbinder { private SimpleAdapter.ViewHolder target; }
- 生成构造函数
if (!bindNeedsView()) { // Add a delegating constructor with a target type + view signature for reflective use. result.addMethod(createBindingViewDelegateConstructor(targetType)); } result.addMethod(createBindingConstructor(targetType));
- 生成unbind方法。 if (hasViewBindings() || !hasParentBinding()) { result.addMethod(createBindingUnbindMethod(result, targetType)); }
return result.build(); }
####(一)生成类名 代码里注释的比较详细了
####(二)生成构造函数 主要是createBindingConstructor 方法,主要是对成员变量赋值,以及设置监听事件。先看下javapoet提供的几个方法或类:
1.MethodSpec:生成方法的辅助类 这里主要是生成构造函数,当然也可以生成其它的普通方法,构造函数也是方法的一种吗。 通过constructorBuilder构造出一个方法,通过addAnnotation添加注解,通过addModifiers添加修饰符。
MethodSpec.Builder constructor = MethodSpec.constructorBuilder() .addAnnotation(UI_THREAD) .addModifiers(PUBLIC);
通过如下方法添加参数targetType为参数类型,”target”为参数的变量名
constructor.addParameter(targetType, "target");
通过如下方法添加代码语句 第一参数是String类型,可以有占位符S(字符串类型),S(字符串类型),T(类型)等 第二个参数Object… args,类型,多参数不固定。 就像你平时使用String.format()方法一样的意思.
constructor.addStatement("this.target = target");
2.构造函数中变量的赋值 这里主要是对使用了如下的代码进行一个赋值操作。
@BindView(R2.id.title) TextView title; @OnClick(R2.id.hello) void sayHello() {}
这里我们主要看一下关键方法,因为都是类似的拼接代码字符串。 我们看一下变量的赋值:
private void addViewBindings(MethodSpec.Builder result, ViewBindings bindings) { if (bindings.isSingleFieldBinding()) { // Optimize the common case where there's a single binding directly to a field. FieldViewBinding fieldBinding = bindings.getFieldBinding(); CodeBlock.Builder builder = CodeBlock.builder() .add("target.$L = ", fieldBinding.getName());
boolean requiresCast = requiresCast(fieldBinding.getType()); if (!requiresCast && !fieldBinding.isRequired()) { builder.add("source.findViewById(L)", bindings.getId().code); } else { builder.add("T.find", UTILS); builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView"); if (requiresCast) { builder.add("AsType"); } builder.add("(source, L", bindings.getId().code); if (fieldBinding.isRequired() || requiresCast) { builder.add(", S", asHumanDescription(singletonList(fieldBinding))); } if (requiresCast) { builder.add(", T.class", fieldBinding.getRawType()); } builder.add(")"); } result.addStatement("L", builder.build()); return; }
List requiredViewBindings = bindings.getRequiredBindings(); if (requiredViewBindings.isEmpty()) { result.addStatement("view = source.findViewById(L)", bindings.getId().code); } else if (!bindings.isBoundToRoot()) { result.addStatement("view = T.findRequiredView(source, S)", UTILS, bindings.getId().code, asHumanDescription(requiredViewBindings)); }
addFieldBindings(result, bindings); addMethodBindings(result, bindings); }
主要是调用系统的findViewById 方法,但是你看到了findRequiredViewAsType,findRequiredView方法和castView方法,findRequiredView,findRequiredViewAsType是作者为乐代码的书写方便对findViewById的一层封装,你可以看一下源码,最后都会调用的findRequiredView方法的findViewById方法。
public static View findRequiredView(View source, @IdRes int id, String who) { View view = source.findViewById(id); if (view != null) { return view; } String name = getResourceEntryName(source, id); throw new IllegalStateException("Required view '"
- name
- "' with ID "
- id
- " for "
- who
- " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
- " (methods) annotation."); }
这个castView是什么方法呢?是Class类的方法,直接转换为指定的类型
public static T castView(View view, @IdRes int id, String who, Class cls) { try { return cls.cast(view); } catch (ClassCastException e) { String name = getResourceEntryName(view, id); throw new IllegalStateException("View '"
- name
- "' with ID "
- id
- " for "
- who
- " was of the wrong type. See cause for more info.", e); } }
说白了都是调用系统的方法。 好了到这里成员变量的赋值算是完了。 注意一点target.title target就是我们的activity或者view ;也验证了为什么是使用了类似BindView注解不能是private修饰符的另一个原因了。
接下来是方法的监听 private void addMethodBindings(MethodSpec.Builder result, ViewBindings bindings) {}方法,李 main 也是通过循环添加方法借助我们上文提到的 MethodSpec.methodBuilder构造器
for (ListenerMethod method : getListenerMethods(listener)) { MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder(method.name()) .addAnnotation(Override.class) .addModifiers(PUBLIC) .returns(bestGuess(method.returnType())); String[] parameterTypes = method.parameters(); for (int i = 0, count = parameterTypes.length; i < count; i++) { callbackMethod.addParameter(bestGuess(parameterTypes[i]), "p" + i); //... }
感兴趣的可以根据生成的代码来对照这查看,这里就不多说了。 最后生成的如下所示的代码。
@UiThread public SimpleActivity_ViewBinding(final T target, View source) { this.target = target;
View view; target.title = Utils.findRequiredViewAsType(source, R.id.title, "field 'title'", TextView.class); target.subtitle = Utils.findRequiredViewAsType(source, R.id.subtitle, "field 'subtitle'", TextView.class); view = Utils.findRequiredView(source, R.id.hello, "field 'hello', method 'sayHello', and method 'sayGetOffMe'"); target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class); view2130968578 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.sayHello(); } }); view.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View p0) { return target.sayGetOffMe(); } }); view = Utils.findRequiredView(source, R.id.list_of_things, "field 'listOfThings' and method 'onItemClick'"); target.listOfThings = Utils.castView(view, R.id.list_of_things, "field 'listOfThings'", ListView.class); view2130968579 = view; ((AdapterView) view).setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView p0, View p1, int p2, long p3) { target.onItemClick(p2); } }); target.footer = Utils.findRequiredViewAsType(source, R.id.footer, "field 'footer'", TextView.class); target.headerViews = Utils.listOf( Utils.findRequiredView(source, R.id.title, "field 'headerViews'"), Utils.findRequiredView(source, R.id.subtitle, "field 'headerViews'"), Utils.findRequiredView(source, R.id.hello, "field 'headerViews'")); }
####(三)生成unbind方法 createBindingUnbindMethod方法主要是把的成员变量啦,Listener等 置为空,比如setOnClickListener(null)。
private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass, TypeName targetType) { MethodSpec.Builder result = MethodSpec.methodBuilder("unbind") .addAnnotation(Override.class) .addModifiers(PUBLIC); if (!isFinal && !hasParentBinding()) { result.addAnnotation(CALL_SUPER); } boolean rootBindingWithFields = !hasParentBinding() && hasFieldBindings(); if (hasFieldBindings() || rootBindingWithFields) { result.addStatement("T target = this.target", targetType); } if (!hasParentBinding()) { String target = rootBindingWithFields ? "target" : "this.target"; result.addStatement("if (N == null) throw new S)", target, IllegalStateException.class, "Bindings already cleared."); } else { result.addStatement("super.unbind()"); }
if (hasFieldBindings()) { result.addCode("\n"); for (ViewBindings bindings : viewIdMap.values()) { if (bindings.getFieldBinding() != null) { result.addStatement("target.L = null", bindings.getFieldBinding().getName()); } } for (FieldCollectionViewBinding fieldCollectionBinding : collectionBindings.keySet()) { result.addStatement("target.L = null", fieldCollectionBinding.getName()); } }
if (hasMethodBindings()) { result.addCode("\n"); for (ViewBindings bindings : viewIdMap.values()) { addFieldAndUnbindStatement(bindingClass, result, bindings); } }
if (!hasParentBinding()) { result.addCode("\n"); result.addStatement("this.target = null"); }
return result.build(); }
主要就是addStatement方法,上文已经说了,该方法的意思就是生成一句代码, 第一参数是String类型,可以有占位符S(字符串类型),S(字符串类型),T(类型)等 第二个参数Object… args,类型,多参数不固定。 就像你平时使用String.format()方法一样的意思. 比较简单,最后生成的方法类似:
@Override public void unbind() { SimpleAdapter.ViewHolder target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.word = null; target.length = null; target.position = null;
this.target = null; }
最后我们看下对外提供的API。
bind 方法
那么还差一步,什么时候都要我们生成的java文件呢? 答案是: ButterKnife.bind(this);方法。 我们看一下ButterKnife对外提供的API
/**
- BindView annotated fields and methods in the specified {@link Activity}. The current content
- view is used as the view root.
- @param target Target activity for view binding. */ @NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView); }
/**
- BindView annotated fields and methods in the specified {@link View}. The view and its children
- are used as the view root.
- @param target Target view for view binding. */ @NonNull @UiThread public static Unbinder bind(@NonNull View target) { return createBinding(target, target); }
/**
- BindView annotated fields and methods in the specified {@link Dialog}. The current content
- view is used as the view root.
- @param target Target dialog for view binding. */ @NonNull @UiThread public static Unbinder bind(@NonNull Dialog target) { View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView); }
/**
- BindView annotated fields and methods in the specified {@code target} using the {@code source}
- {@link Activity} as the view root.
- @param target Target class for view binding.
- @param source Activity on which IDs will be looked up. */ @NonNull @UiThread public static Unbinder bind(@NonNull Object target, @NonNull Activity source) { View sourceView = source.getWindow().getDecorView(); return createBinding(target, sourceView); }
/**
- BindView annotated fields and methods in the specified {@code target} using the {@code source}
- {@link View} as the view root.
- @param target Target class for view binding.
- @param source View root on which IDs will be looked up. */ @NonNull @UiThread public static Unbinder bind(@NonNull Object target, @NonNull View source) { return createBinding(target, source); }
/**
- BindView annotated fields and methods in the specified {@code target} using the {@code source}
- {@link Dialog} as the view root.
- @param target Target class for view binding.
- @param source Dialog on which IDs will be looked up. */ @NonNull @UiThread public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) { View sourceView = source.getWindow().getDecorView(); return createBinding(target, sourceView); }
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { Class<?> targetClass = target.getClass(); if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName()); Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) { return Unbinder.EMPTY; }
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type. try { return constructor.newInstance(target, source); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InstantiationException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException("Unable to create binding instance.", cause); } }
我们看到bind的一系列方法都会调用createBinding方法
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) { Class<?> targetClass = target.getClass(); if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName()); Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) { return Unbinder.EMPTY; }
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type. try { return constructor.newInstance(target, source); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InstantiationException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException("Unable to create binding instance.", cause); } }
Class<?> (?是转义字符csdn要不支持)targetClass = target.getClass();获取类的实例,最后获取构造函数,最后constructor.newInstance方法来调用该类的构造函数。 而该类的构造函数是通过findBindingConstructorForClass方法,我可没来看下此方法:
@Nullable @CheckResult @UiThread private static Constructor findBindingConstructorForClass(Class cls) { //缓存中查找,找到直接返回 Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls); if (bindingCtor != null) { if (debug) Log.d(TAG, "HIT: Cached in binding map."); return bindingCtor; }
//检查合法性 String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.")) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return null; } try { //构造一个class,可以看类名就是我们生成的。 Class<?> bindingClass = Class.forName(clsName + "_ViewBinding"); //noinspection unchecked // 获取我们的构造函数 bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class); if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor."); } catch (ClassNotFoundException e) { if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName()); bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); } catch (NoSuchMethodException e) { throw new RuntimeException("Unable to find binding constructor for " + clsName, e); } //加入到缓存中 BINDINGS.put(cls, bindingCtor); return bindingCtor; }
可以看到用到了简单的缓存。
unbind 方法
在新版的8.4.0中去除了 unbind方法。
ButterKnife.unbind(this);
采用了接口的形式。让生成的类来实现,比如:
public final class SimpleAdapterViewHolder_ViewBinding implements Unbinder { @UiThread public SimpleAdapterViewHolder_ViewBinding(SimpleAdapter.ViewHolder target, View source) { //... } //... @Override public void unbind() {
最后
写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。 由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的点击这里>Android IOC架构设计免费获取。 群内还有免费的高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。