ButterKnife 是一款知名老牌 Android 开发框架,通过注解绑定视图,避免了 findViewById() 的操作,广受好评!由于它是在编译时对注解进行解析完成相关代码的生成,所以在项目编译时会略耗时,但不会影响运行时的性能。
简单使用
在Activity中通过注解@BindView
绑定视图,@onClick
绑定事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MainActivity extends AppCompatActivity { @BindView(R.id.tv_title) TextView title;
@OnClick(R.id.bt) public void onClick() { title.setText("hello world"); }
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); } }
|
原理
ButterKnife和Activity是从ButterKnife.bind(this)
建立绑定关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return bind(target, sourceView); }
@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 { return constructor.newInstance(target, source); } }
|
根据 Class 得到一个继承了Unbinder
的Constructor
,最后通过反射constructor.newInstance(target, source)
得到Unbinder
子类的一个实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Nullable @CheckResult @UiThread private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) { 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(); try { Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class); } catch (ClassNotFoundException e) { 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
是否存在 Class 对应的 Constructor,如果存在则直接返回,否则去构造对应的 Constructor。其中BINDINGS
是一个LinkedHashMap
: Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>()
,缓存了对应的 Class 和 Constructor 以提高效率!
1
| Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
|
最终bind()
方法返回的是MainActivity_ViewBinding
类的实例。
MainActivity_ViewBinding类是在编译期由annotationProcessor生成的。文件内容如下:
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
| public class MainActivity_ViewBinding implements Unbinder { private MainActivity target;
private View view213213213;
@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.title = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'title'", TextView.class); view = Utils.findRequiredView(source, R.id.bt, "method 'onClick'"); view213213213 = 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.title = null;
view213213213.setOnClickListener(null); view213213213 = null; } }
|
核心还是利用findViewById()
转成指定类型的 View,并完成事件绑定。
注解处理器
注解处理器参考注解annotation 和 注解 in kotlin。在ButterKnife中使用如下:
1 2 3 4 5
| @Retention(RUNTIME) @Target(FIELD) public @interface BindView { @IdRes int value(); }
|
#ButterKnifeProcessor 注解器
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
| @AutoService(Processor.class) @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC) @SuppressWarnings("NullAway") public final class ButterKnifeProcessor extends AbstractProcessor { private static final String OPTION_SDK_INT = "butterknife.minSdk"; private static final String OPTION_DEBUGGABLE = "butterknife.debuggable"; static final Id NO_ID = new Id(NO_RES_ID); static final String VIEW_TYPE = "android.view.View"; static final String ACTIVITY_TYPE = "android.app.Activity"; static final String DIALOG_TYPE = "android.app.Dialog"; @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; } }
|