butterknife是一个Android View和Callback注入框架,相信很多人都在使用,可以减少很多代码量,并且避免遗漏绑定而产生的异常。
1. 介绍
1.1 优点
- 通过注释@BindView来消除findViewById
- 通过注释@OnClick或者其他来消除绑定事件
- 通过资源注释消除资源查找。
一句话概括,用自动生成的代码来帮助处理以上事情,减少开发者的代码量。
2. 基本用例
因为比较简单,可以直接参考butterknife官方介绍。下面就以最常用的View绑定和点击事件绑定为例。
2.1 使用@BindView绑定View
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tvResult) TextView tvResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
tvResult.setText("Test");
}
}
2.2 使用@OnClick绑定点击事件
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.btnFinish)
void btnFinishClick() {
finish();
}
}
3. 源码分析
从上图我们可以看到,butterknife的源码主要由2部分组成,一部分是编译期自动生成代码,另一部分是通过自动生成的代码对View或者点击事件进行绑定。
3.2 自动代码生成
我们可以看到,每一个Activity(或者Fragment等),生成2个类,分别是"ClassName_ViewBinder"和“ClassName_ViewBinding”。
生成代码
主要用了代码生成框架auto,以后会专门结合ButterKnife写一篇文章,本文点到为止,你只要知道解析注解,通过Auto框架生成了以上的Java文件,不做具体展开。
主要逻辑在ButterKnifeProcessor里的process接口
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
//寻找并且生成BindingClass
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
//依次生成Java文件
for (JavaFile javaFile : bindingClass.brewJava()) {
try {
javaFile.writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
e.getMessage());
}
}
}
return true;
}
3.3 View绑定
通过ButterKnife进行关联
ButterKnife.bind(this);
public static Unbinder bind(@NonNull Activity target) {
return getViewBinder(target).bind(Finder.ACTIVITY, target, target);
}
根据Activity获取ViewBinder
static ViewBinder<Object> getViewBinder(@NonNull Object target) {
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
return findViewBinderForClass(targetClass);
}
根据class名称获取ViewBinder
private static ViewBinder<Object> findViewBinderForClass(Class<?> cls) {
//根据cls获取缓存的ViewBinder
ViewBinder<Object> viewBinder = BINDERS.get(cls);
//如果缓存存在,则直接返回使用
if (viewBinder != null) {
if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
return viewBinder;
}
String clsName = cls.getName();
//如果类名是以android或者java开头,则认为是框架类,不作处理
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return NOP_VIEW_BINDER;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
//根据clsName + "_ViewBinder" 实例化
Class<?> viewBindingClass = Class.forName(clsName + "_ViewBinder");
//noinspection unchecked
viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
} catch (ClassNotFoundException e) {
if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
//如果没有找到该类,则实例化父类
viewBinder = findViewBinderForClass(cls.getSuperclass());
} catch (InstantiationException e) {
throw new RuntimeException("Unable to create view binder for " + clsName, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to create view binder for " + clsName, e);
}
//将实例化的viewBinder进行缓存
BINDERS.put(cls, viewBinder);
return viewBinder;
}
调用MainActivity_ViewBinder的bind
public final class MainActivity_ViewBinder implements ViewBinder<MainActivity> {
@Override
public Unbinder bind(Finder finder, MainActivity target, Object source) {
return new MainActivity_ViewBinding<>(target, finder, source);
}
}
实例化ClassName_ViewBinding类
public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
protected T target;
private View view2131492946;
public MainActivity_ViewBinding(final T target, Finder finder, Object source) {
this.target = target;
View view;
//查找View,并赋值给Activity的tvResult
target.tvResult = finder.findRequiredViewAsType(source, R.id.tvResult, "field 'tvResult'", TextView.class);
...省略代码
}
}
查找View
public final <T> T findRequiredViewAsType(Object source, @IdRes int id, String who,
Class<T> cls) {
//查找View
View view = findRequiredView(source, id, who);
return castView(view, id, who, cls);
}
public final View findRequiredView(Object source, @IdRes int id, String who) {
//查找类
View view = findOptionalView(source, id);
if (view != null) {
return view;
}
...省略代码
}
ACTIVITY {
@Override public View findOptionalView(Object source, @IdRes int id) {
//通过Activity查找View
return ((Activity) source).findViewById(id);
}
...省略代码
}
至此完成了View的绑定流程,点击事件和资源文件绑定流程大同小异,我就不一一介绍了,感兴趣的可以自己看下。
6. 参考资料
butterknife
butterknife官方介绍
auto
可以随意转发,也欢迎关注我的简书,我会坚持给大家带来分享。