动机
一开始感觉ButterKnife
通过注解来加载控件和设置监听器会在运行过程中,利用反射来执行,这样的话,会不会导致启动的时候会比较卡!! 然后就称此机会来学习ButterKnife
的源码。本文基于butterknife-7.0.1版本进行学习。
例子
首先看一个,使用ButterKnife
的简单例子:
@Bind(R.id.button)Button mButton;
@Override
protected voidon Create(Bundlesaved InstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.button) void clickButton() {
Toast.makeText(MainActivity.this,“HelloWorld!”,Toast.LENGTH_SHORT).show();
}
入口
在上面例子中可以看到,
ButterKnife.bind(this);
是整个ButterKnife
的入口,下面我们进去看看源代码:
public static void bind(Activity target) {
bind(target, target, Finder.ACTIVITY);
}
这里有很多的bind
方法:
// 用于Fragment的绑定
public static void bind(Object target, View source) {
bind(target, source, Finder.VIEW);
}
但是所有的bind
都去调用下面这个:
static void bind(Object target, Object source, Finder finder) {
Class<?> targetClass = target.getClass();
try {
if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
if (viewBinder != null) {
viewBinder.bind(finder, target, source);
}
} catch (Exception e) {
throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
}
}
首先我们找到target
的类对象,然后调用findViewBinderForClass
来寻找其类对象的ViewBinder
。那么什么是ViewBinder
呢?
对于每一个类,如果其调用了ButterKnife.bind()
方法,那么在编译的过程中,JVM就会为其生成一个ViewBinder
对象,该ViewBinder
里面存储了所有的@OnClick``@Bind
等注解过的对象的一些操作(例如某个按钮的点击事件),这个ViewBinder
等哈会在说,这里只要知道就好。
然后,我们进如findViewBinderForClass
:
private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
throws IllegalAccessException, InstantiationException {
// 在内存里面进行查找
ViewBinder<Object> viewBinder = BINDERS.get(cls);
if (viewBinder != null) {
if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
return viewBinder;
}
// 判断是否为框架类(android 或 java为前缀的包)
String clsName = cls.getName();
if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return NOP_VIEW_BINDER;
}
try {
// 实例化该类中的ViewBinder(形如MainActivity$$ViewBinder)
Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
//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());
}
// 放入内存
BINDERS.put(cls, viewBinder);
return viewBinder;
}
上面代码中一些静态变量如下:
static final Map<Class<?>, ViewBinder<Object>> BINDERS = new LinkedHashMap<>();
public static final String SUFFIX = "$$ViewBinder";
public static final String ANDROID_PREFIX = "android.";
public static final String JAVA_PREFIX = "java.";
可以看到,对于一个类(MainAcitivty
),如果其调用了ButterKnife.bind()
方法,那么在编译的过程中,JVM就会为其生成一个MainAcitivty$$ViewBinder
对象,如下:
public class MainActivity$$ViewBinder<T extends com.spirittalk.rxjavatraining.MainActivity> implements ViewBinder<T> {
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131492970, "field 'mButton' and method 'clickButton'");
target.mButton = finder.castView(view, 2131492970, "field 'mButton'");
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override public void doClick(android.view.View p0) {
target.clickButton();
}
});
}
@Override public void unbind(T target) {
target.mButton = null;
}
}
这样就可以将MainActivity$$ViewBinder
里面的方法来绑定到MainActivity
里面去,mButton
的点击事件,调用target.clickButton
(所以clickButton
不能为private
,不然就不会被调用,因为这样的调用方法不是反射)。
ViewBinder
那么ViewBinder
是怎么生成的呢?什么时候生成的呢?
这里就需要Java的一种技术:注解处理器,具体的内容可以看这里,在ButterKnife
中可以在
可以看到如下内容
butterknife.internal.ButterKnifeProcessor
那么我们去ButterKnifeProcessor
看一看:
首先看看getSupportedAnnotationTypes
,其表示这个注解处理器可以处理那些注解:
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<String>();
types.add(Bind.class.getCanonicalName());
for (Class<? extends Annotation> listener : LISTENERS) {
types.add(listener.getCanonicalName());
}
types.add(BindBool.class.getCanonicalName());
types.add(BindColor.class.getCanonicalName());
types.add(BindDimen.class.getCanonicalName());
types.add(BindDrawable.class.getCanonicalName());
types.add(BindInt.class.getCanonicalName());
types.add(BindString.class.getCanonicalName());
return types;
}
LISTENERS
如下:
private static final List<Class<? extends Annotation>> LISTENERS = Arrays.asList(//
OnCheckedChanged.class, //
OnClick.class, //
OnEditorAction.class, //
OnFocusChange.class, //
OnItemClick.class, //
OnItemLongClick.class, //
OnItemSelected.class, //
OnLongClick.class, //
OnPageChange.class, //
OnTextChanged.class, //
OnTouch.class //
);
主要就是process
方法:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
// 找到所有注解
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
// 生成Java文件
try {
JavaFileObject jfo = filer.createSourceFile(bindingClass.getFqcn(), typeElement);
Writer writer = jfo.openWriter();
writer.write(bindingClass.brewJava());
writer.flush();
writer.close();
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
e.getMessage());
}
}
return true;
}
然后进入findAndParseTargets
,部分代码
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<TypeElement, BindingClass>();
Set<String> erasedTargetNames = new LinkedHashSet<String>();
// Process each @Bind element.
for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
try {
parseBind(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, Bind.class, e);
}
}
return targetClassMap;
}
然后parseBind
private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<String> erasedTargetNames) {
/*
* isInaccessibleViaGeneratedCode()中验证了:
* 1、修饰符不能为private或static;2、不能用于非Class类;3、当前类修饰符不能为private
*
* isBindingInWrongPackage()验证了,注解Class不能位于Android framework package或Java framework package。
*/
if (isInaccessibleViaGeneratedCode(Bind.class, "fields", element)
|| isBindingInWrongPackage(Bind.class, element)) {
return;
}
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.ARRAY) {
parseBindMany(element, targetClassMap, erasedTargetNames);
// @Bind({ R.id.consume_checkbox, R.id.expired_checkbox, R.id.latest_push_checkbox}) List<CheckedTextView> checkedTextViews;
parseBindMany(element, targetClassMap, erasedTargetNames);
} else if (isSubtypeOfType(elementType, ITERABLE_TYPE)) {
error(element, "@%s must be a List or array. (%s.%s)", Bind.class.getSimpleName(),
((TypeElement) element.getEnclosingElement()).getQualifiedName(),
element.getSimpleName());
} else {
// @Bind(R.id.button)
parseBindOne(element, targetClassMap, erasedTargetNames);
}
}
在看看parseBindOne
private void parseBindOne(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<String> erasedTargetNames) {
boolean hasError = false;
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
// Verify that the target type extends from View.
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
Bind.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName());
hasError = true;
}
// Assemble information on the field.
int[] ids = element.getAnnotation(Bind.class).value();
if (ids.length != 1) {
error(element, "@%s for a view must only specify one ID. Found: %s. (%s.%s)",
Bind.class.getSimpleName(), Arrays.toString(ids), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
if (hasError) {
return;
}
int id = ids[0];
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass != null) {
ViewBindings viewBindings = bindingClass.getViewBinding(id);
if (viewBindings != null) {
Iterator<FieldViewBinding> iterator = viewBindings.getFieldBindings().iterator();
if (iterator.hasNext()) {
FieldViewBinding existingBinding = iterator.next();
error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
Bind.class.getSimpleName(), id, existingBinding.getName(),
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
}
} else {
bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
}
String name = element.getSimpleName().toString();
String type = elementType.toString();
boolean required = isRequiredBinding(element);
FieldViewBinding binding = new FieldViewBinding(name, type, required);
bindingClass.addField(id, binding);
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement.toString());
}
这里主要看看getOrCreateTargetClass
,这里就是真正的构造要生成的Java代码的结构。
private BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap,
TypeElement enclosingElement) {
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass == null) {
String targetType = enclosingElement.getQualifiedName().toString();
String classPackage = getPackageName(enclosingElement);
String className = getClassName(enclosingElement, classPackage) + SUFFIX;
bindingClass = new BindingClass(classPackage, className, targetType);
targetClassMap.put(enclosingElement, bindingClass);
}
return bindingClass;
}
其主要的结构如下:
Map<TypeElement , BindingClass> targetClassMap
TypeElement -> 对于每一个类
BindingClass -> Map<Integer, ViewBindings>
Integer -> 被{@Bind}注解的控件ID
ViewBindings -> 每一个控件的内容(FieldViewBinding,MethodViewBinding)
好这里面的逻辑关系只需知道就可以,没必要全都弄清楚。
关于绑定
ButterKnife
主要就是在编译过程中,根据注解重新生成一个ViewBinder
类,在运行过程中,通过反射将ViewBinder
实例化,被注解的元素可以调用ViewBinder
里面的方法了。这或许就是一种绑定的方法,将ViewBinder
绑定到Acvitivty
或Fragment
上面。绑定实在运行的时候进行的(ViewBinder
在编译时生成的)
参考
http://www.jianshu.com/p/95d4f0eb6027
http://www.jianshu.com/p/4c38616af3a5
我的邮箱:1906362072@qq.com