ButterKnife自定义了很多我们常用的注解,比如@BindView和@OnClick。现在先来看@BindView的源码,如下所示:
@Retention(RUNTIME) @Target(FIELD) public @interface BindView { /** View ID to which the field will be bound. */ @IdRes int value(); }
Retention(CLASS)表明@BindView 注解是编译时注解,@Target(FIELD)则表明@BindView注解应用于成员变量。
接下来使用@BindView注解来绑定TextView 控件,后文会用到这些代码,如下所示:
public class MainActivity extends AppCompatActivity { @BindView(R.id.tv_text) TextView tvText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); } }
ButterKnifeProcessor源码分析
要处理注解需要注解处理器,ButterKnife的注解处理器是ButterKnifeProcessor,它的主要处理逻辑都在process方法中,源码如下所示:
@AutoService(Processor.class) public final class ButterKnifeProcessor extends AbstractProcessor { ...... @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { // findAndParseTargets方法主要用于 查找和解析注解。 Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); // 遍历findAndParseTargets方法返回的Map集合 for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); // 得到BindingSet的值,并调用了它的brewJava方法 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 true; } ...... }
ButterKnifeProcessor继承自AbstractProcessor,我们再来看看findAndParseTargets方法:
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>(); Set<TypeElement> erasedTargetNames = new LinkedHashSet<>(); ...... for (Element element : env.getElementsAnnotatedWith(BindView.class)) { try { parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } } ...... return bindingMap; }
findAndParseTargets 方法会查找所有 ButterKnife 的注解来进行解析,这里只看处理@BindView注解的部分。然后查看parseBindView方法,如下所示
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); /** * isInaccessibleViaGeneratedCode方法里面检查了3个点,它们分别是: * 1、方法修饰符不能为private和static; * 2、包含类型不能为非Class; * 3、包含类的修饰符不能是private。 */ */ boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element); ...... if (hasError) { return; } // 获取注解的标注的值。 int id = element.getAnnotation(BindView.class).value(); BindingSet.Builder builder = builderMap.get(enclosingElement); Id resourceId = elementToId(element, BindView.class, id); // 判断是否存在BindingSet.Builder的值,若没有则创建,有则复用。 if (builder != null) { String existingBindingName = builder.findExistingBindingName(resourceId); if (existingBindingName != null) { error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)", BindView.class.getSimpleName(), id, existingBindingName, enclosingElement.getQualifiedName(), element.getSimpleName()); return; } } else { builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString(); TypeName type = TypeName.get(elementType); boolean required = isFieldRequired(element); // 将注解修饰的类型的信息存储在 FieldViewBinding中,并将FieldViewBinding传入 BindingSet.Builder的addField方法中。 builder.addField(resourceId, new FieldViewBinding(name, type, required)); erasedTargetNames.add(enclosingElement); }
可以看出,该方法主要用于检查类型和修饰符以及包名等,然后将注解修饰的类型的信息进行存储。
接下来我们回到process方法,brewJava方法将使用注解的类生成一个JavaFile,然后将该JavaFile输出成Java文件。(javaFile.writeTo(filer))
在build-generared-source-apt目录下可以找到生成的Java文件,这里生成的文件名为MainActivity_ViewBinding。
ButterKnife的bind方法
为了使用ButterKnife,我们需要用ButterKnife.bind()方法来绑定上下文。现在先来查看bind方法:
@NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return bind(target, sourceView); }
bind 方法有很多重载方法,上面的代码只是其中的一种,也就是传入 Activity的情况。得到Activity的DecorView,并将DecorView和Activity传入bind方法中:
@NonNull @UiThread public static Unbinder bind(@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 { // MainActivity_ViewBinding实例,并将Activity和其DecorView作为参数传入构造函数 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方法获取Activity构造函数,方法如下所示:
@Nullable @CheckResult @UiThread private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { // 先从BINDINGS中获取对应Class的Constructor实例,BINDINGS是一个Class为key、Constructor为value的Map。 Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls); if (bindingCtor != null || BINDINGS.containsKey(cls)) { if (debug) Log.d(TAG, "HIT: Cached in binding map."); return bindingCtor; } String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.") || clsName.startsWith("androidx.")) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return null; } try { // 通过反射来生成Class类,这个Class类就是我们此前生成的MainActivity_ViewBinding。 Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); // 调用getConstructor方法将Class转换为Constructor, 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); } // 处将Constructor作为value,Class作为key存储在BINDINGS中,最后返回该Constructor。 BINDINGS.put(cls, bindingCtor); return bindingCtor; }
代码注释中使用到了反射的方式来创建MainActivity_ViewBinding,反射会影响性能,但是因为有 BINDERS的存在(一个类只会在第一次反射生成,以后会从BINDERS中去取),也可以解决一些性能问题。
生成的辅助类分析
MainActivity_ViewBinding.java,在app\build\generated\ap_generated_sources\debug\out\com\legend\butterknife文件夹下,代码如下所示:
public class MainActivity_ViewBinding implements Unbinder { private MainActivity target; // source值就是MainActivity的 DecorView,而target值为MainActivity @UiThread public MainActivity_ViewBinding(MainActivity target) { this(target, target.getWindow().getDecorView()); } @UiThread public MainActivity_ViewBinding(MainActivity target, View source) { this.target = target; // 调用了Utils的findRequiredViewAsType方法并将source值、R.id.tv_text等参数传入,findRequiredViewAsType方法 target.tvText = Utils.findRequiredViewAsType(source, R.id.tv_text, "field 'tvText'", TextView.class); } @Override @CallSuper public void unbind() { MainActivity target = this.target; if (target == null) throw new IllegalStateException("Bindings already cleared."); this.target = null; target.tvText = null; } }
在前面我们已经知道在 bind方法中调用 newInstance 方法生成 MainActivity_ViewBinding实例时传入的source值是MainActivity的DecorView。而target值为MainActivity。
注释处,findRequiredViewAsType传入source的值和R.id.tv_text等参数,让我们看看该方法:
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); }
该方法调用findRequiredView方法,代码如下所示:
public static View findRequiredView(View source, @IdRes int id, String who) { View view = source.findViewById(id); if (view != null) { return view; } ...... }
findRequiredView方法调用了DecorView的findViewById方法并将R.id.tv_text对应的View返回。再看看castView方法:
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); ...... } }
castView方法会将View强制转换为TextView并返回。再次回到MainActivity_ViewBinding辅助类,这个返回的TextView会赋值给target,也就是MainActivity,这样我们在MainActivity中就可以使用这个TextView。
总结
ButterKnifeProcessor主要用于查找所有的注解,并生成相应的xxxx_ViewBingding类,然后在ButterKnife的bind方法来通过反射创建xxx_ViewBinding类的同时,将Activity的引用和DecorView的引用通过构造函数的方式传入,通过DecorView的findViewById方法拿到View的实例,然后在castView方法中进行强转并返回。