本博客介绍ButterKnife的使用及其源码解析。
ButterKnife的使用
ButterKnife简介
添加依赖
在Project级别的build.gradle
文件中添加为ButterKnife定制的Gradle插件:
1 2 3 4 5 6 7 8 9
| buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' classpath 'com.jakewharton:butterknife-gradle-plugin:8.8.1' } }
|
在Application级别的build.gradle
文件中添加ButterKnife插件和依赖代码:
1 2 3 4 5 6
| apply plugin: 'com.jakewharton.butterknife' ... dependencies{ annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' compile 'com.android.support:recyclerview-v7:26.0.0-alpha1' }
|
使用ButterKnife
用注解@BindView()
绑定单个控件id,用注解@BindViews()
绑定多个控件id:
1 2 3 4 5 6
| (R.id.et_input) EditText etInput; (R.id.btn_send) Button btnSend; (R.id.lv_main) ListView lvMain;
|
在Activity中,需要在setContentView()
之后添加:
1
| mUnbinder = ButterKnife.bind(this);
|
切记在Activity的onDestroy()
中进行解绑:
在Fragment中,需要在onCreateView()
的return之前添加:
1
| mUnbinder = ButterKnife.bind(this, view);
|
在Fragment的onDestroyView()
中进行以下操作:
而在ListView或者RecyclerView的Adapter中也可以使用ButterKnife,需要做以下操作:
1 2 3 4
| public (View itemView) { super(itemView); ButterKnife.bind(this, itemView); }
|
ButterKnife提供了以下资源绑定方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @BindAnim() @BindArray() @BindBitmap() @BindBool() @BindColor() @BindDimen() @BindDrawable() @BindFloat @BindFont @BindInt @BindString
|
ButterKnife提供了以下监听绑定方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @OnCheckedChanged() @OnClick() @OnEditorAction() @OnFocusChange() @OnItemClick @OnItemLongClick() @OnItemSelected() @OnLongClick() @OnPageChange() @OnTextChanged() @OnTouch()
|
ButterKnife源码解析
ButterKnife采用的是编译时注解,自定义了很多常用注解,上文已经讲解在此不再赘述。以@BindView
注解为例:
1 2 3 4
| @Retention(CLASS) @Target(FIELD) public @interface BindView { @IdRes int value(); }
|
其中@Retention(CLASS)
用来声明注解的保留策略,表明@BindView
注解是编译时注解,而@Target(FIELD)
则表明@BindView
注解作用于成员变量。
解析ButterKnife注解处理器ButterKnifeProcessor
要处理注解需要使用到注解处理器,ButterKnife的注解处理器是ButterKnifeProcessor
,继承自AbstractProcessor
。
1 2 3 4
| @AutoService(Processor.class) public final class ButterKnifeProcessor extends AbstractProcessor { ... }
|
在注解处理器ButterKnifeProcessor
中注解处理的主要逻辑都在process()
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk, debuggable); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; }
|
接下来查看在process()
中调用的findAndParseTargets()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); scanForRClasses(env); for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceAnimation(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindAnim.class, e); } } ...(此处省略n多个@BindXxx注解) Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries = new ArrayDeque<>(builderMap.entrySet()); Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>(); while (!entries.isEmpty()) { Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst(); TypeElement type = entry.getKey(); BindingSet.Builder builder = entry.getValue(); TypeElement parentType = findParentType(type, erasedTargetNames); if (parentType == null) { bindingMap.put(type, builder.build()); } else { BindingSet parentBinding = bindingMap.get(parentType); if (parentBinding != null) { builder.setParent(parentBinding); bindingMap.put(type, builder.build()); } else { entries.addLast(entry); } } } return bindingMap; }
|
接下来查阅parseResourceAnimation()
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| private void parseResourceAnimation(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); if (!ANIMATION_TYPE.equals(element.asType().toString())) { error(element, "@%s field type must be 'Animation'. (%s.%s)", BindAnim.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } hasError |= isInaccessibleViaGeneratedCode(BindAnim.class, "fields", element); hasError |= isBindingInWrongPackage(BindAnim.class, element); if (hasError) { return; } String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindAnim.class).value(); QualifiedId qualifiedId = elementToQualifiedId(element, id); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addResource(new FieldAnimationBinding(getId(qualifiedId), name)); erasedTargetNames.add(enclosingElement); }
|
其中,parseResourceAnimation()
中isInaccessibleViaGeneratedCode()
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 大专栏 ButterKnife的使用及其解析="code">private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass, String targetThing, Element element) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); Set<Modifier> modifiers = element.getModifiers(); if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) { error(element, "@%s %s must not be private or static. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } if (enclosingElement.getKind() != CLASS) { error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } if (enclosingElement.getModifiers().contains(PRIVATE)) { error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } return hasError; }
|
其中,parseResourceAnimation()
中isBindingInWrongPackage()
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass, Element element) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); String qualifiedName = enclosingElement.getQualifiedName().toString(); if (qualifiedName.startsWith("android.")) { error(element, "@%s-annotated class incorrectly in Android framework package. (%s)", annotationClass.getSimpleName(), qualifiedName); return true; } if (qualifiedName.startsWith("java.")) { error(element, "@%s-annotated class incorrectly in Java framework package. (%s)", annotationClass.getSimpleName(), qualifiedName); return true; } return false; }
|
解析ButterKnife的bind()
ButterKnife的bind()运行在UI线程,在指定的Activity中注释变量和方法,当前的内容视图被用作根视图。
1 2 3 4 5 6 7
| @NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView); }
|
接下来查阅createBinding()
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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; } 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); } }
|
下面查阅findBindingConstructorForClass()
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Nullable @CheckResult @UiThread private static Constructor<? extends Unbinder> 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<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); 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; }
|
而上段代码中多次出现的BINDINGS
是什么样的数据结构呢?
1 2
| @VisibleForTesting static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
|
解析ButterKnife生成辅助类
在上问分析中已经生成了<类名>_ViewBinding的类,下面分析<类名>_ViewBinding类中的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class MainActivity_ViewBinding implements Unbinder { private MainActivity target; private View view2131427424; @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; target.etInput = Utils.findRequiredViewAsType(source, R.id.et_input, "field 'etInput'", EditText.class); view = Utils.findRequiredView(source, R.id.btn_send, "field 'btnSend' and method 'OnClick'"); target.btnSend = Utils.castView(view, R.id.btn_send, "field 'btnSend'", Button.class); view2131427424 = view; view.setOnClickListener(new DebouncingOnClickListener() { @Override public void doClick(View p0) { target.OnClick(); } }); } @Override @CallSuper public void unbind() { MainActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); this.target = null; target.etInput = null; target.btnSend = null; view2131427424.setOnClickListener(null); view2131427424 = null; } }
|
下面查阅Utils中的findRequiredViewAsType()
:
1 2 3 4 5 6 7
| public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who, Class<T> cls) { View view = findRequiredView(source, id, who); return castView(view, id, who, cls); }
|
下面查阅Utils中findRequiredView()
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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."); }
|
下面查阅Utils中castView()
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static <T> T castView(View view, @IdRes int id, String who, Class<T> 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); } }
|