标签:
使用Butter Knife己经很长时间了,一直很喜欢用。因为它能帮我节省很多重复性的绑定控件代码。今天就来讲讲它的源码吧。只是总结我对它源码的一个理解。先看下它的使用效果:
public TestActivity extends Activity{
@Bind(R.id.top_pic)
ImageView topPic;
@Bind(R.id.top)
TextView top;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: add setContentView(...) invocation
ButterKnife.bind(this);
}
}
如上它就会自动生成以下的代码。非常方便。
TextView toPic = (TextView)super.findViewById(R.id.top_pic);
对这个不了解的同学可以看下java编程思想中的相关内容,这里简单介绍下,Annotation包含几个注解符:@Retention, @Target, @Inherited, @Documented 。
重点关注下Retention中的参数的含义:
讲完这些知识后,就开始我们的源码之旅吧。
首先我们添加了@Bind(R.id.top_pic)这样的注解,看下@Bind这个注解类:
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
/** View ID to which the field will be bound. */
int[] value();
}
明白了,CLASS,编译时解析。
上面说过编译时自动查找它会调用AbstractProcessor 类中的process方法去处理相关逻辑,这个注解处理什么事情呢?关键找到AbstractProcessor 类。找找找,找到ButterKnifeProcessor。看下它的process方法:
public final class ButterKnifeProcessor extends AbstractProcessor {
@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() ;
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;
}
}
上面代码主要分两步。
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);
}
}
// Process each annotation that corresponds to a listener.
for (Class<? extends Annotation> listener : LISTENERS) {
findAndParseListener(env, listener, targetClassMap , erasedTargetNames);
}
// Process each @BindBool element.
for (Element element : env.getElementsAnnotatedWith( BindBool.class )) {
try {
parseResourceBool(element, targetClassMap, erasedTargetNames) ;
} catch (Exception e) {
logParsingError(element, BindBool.class, e);
}
}
// Process each @BindColor element.
for (Element element : env.getElementsAnnotatedWith( BindColor.class )) {
try {
parseResourceColor(element, targetClassMap, erasedTargetNames) ;
} catch (Exception e) {
logParsingError(element, BindColor.class, e);
}
}
// Process each @BindDimen element.
for (Element element : env.getElementsAnnotatedWith( BindDimen.class )) {
try {
parseResourceDimen(element, targetClassMap, erasedTargetNames) ;
} catch (Exception e) {
logParsingError(element, BindDimen.class, e);
}
}
// Process each @BindDrawable element.
for (Element element : env.getElementsAnnotatedWith( BindDrawable.class )) {
try {
parseResourceDrawable(element, targetClassMap, erasedTargetNames) ;
} catch (Exception e) {
logParsingError(element, BindDrawable.class, e);
}
}
// Process each @BindInt element.
for (Element element : env.getElementsAnnotatedWith( BindInt.class )) {
try {
parseResourceInt(element, targetClassMap, erasedTargetNames) ;
} catch (Exception e) {
logParsingError(element, BindInt.class, e);
}
}
// Process each @BindString element.
for (Element element : env.getElementsAnnotatedWith( BindString.class )) {
try {
parseResourceString(element, targetClassMap, erasedTargetNames) ;
} catch (Exception e) {
logParsingError(element, BindString.class, e);
}
}
// Try to find a parent binder for each.
for (Map.Entry<TypeElement , BindingClass> entry : targetClassMap.entrySet()) {
String parentClassFqcn = findParentFqcn(entry.getKey(), erasedTargetNames) ;
if (parentClassFqcn != null) {
entry.getValue().setParentViewBinder(parentClassFqcn + SUFFIX) ;
}
}
return targetClassMap;
}
解析Binder注释,解析完信息存在targetClassMap中。上面代码有很多解析方式,如解析drawable,解析color等。这里只以parseBind为例进行分析。
private void parseBind(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<String> erasedTargetNames) {
// Verify common generated code restrictions.
if (isInaccessibleViaGeneratedCode( Bind.class, "fields", element)
|| isBindingInWrongPackage(Bind .class, element)) {
return;
}
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.ARRAY) {
parseBindMany(element, targetClassMap, erasedTargetNames) ;
} else if (LIST_TYPE.equals(doubleErasure(elementType))) {
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 {
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); //保存这个bind,后面在生成类文件时候要用到!
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement.toString()) ;
}
所有应用ButterKnife的类都有一个单独的BindingClass与其挂钩,它里面包含了很多信息:Field绑定、Drawable绑定等等很多信息。以类名+”$$ViewBinder”包名标识。FieldViewBinding存放于BindingClass内,用于绑定变量View(与之对应还有绑定Drawable、Bitmap、List、Method等)。记录每个Field的绑定信息,如变量名、类名。每个FieldViewBinding都与一个id对应。注解生成就分析完了,接下来看看是如何生成findViewById之类的代码的。
解析完了,看第二步,brewJava 生成代码。
String brewJava () {
StringBuilder builder = new StringBuilder();
builder.append( "// Generated code from Butter Knife. Do not modify! \n") ;
builder.append( "package ").append(classPackage ).append(";\n\n ");
if (! resourceBindings.isEmpty()) {
builder.append("import android.content.res.Resources; \n") ;
}
if (!viewIdMap.isEmpty() || ! collectionBindings.isEmpty()) {
builder.append("import android.view.View; \n") ;
}
builder.append("import butterknife.ButterKnife.Finder; \n") ;
if ( parentViewBinder == null ) {
builder.append("import butterknife.ButterKnife.ViewBinder; \n") ;
}
builder.append(‘ \n‘) ;
builder.append( "public class " ).append(className) ;
builder.append( "<T extends " ).append(targetClass).append( ">");
if ( parentViewBinder != null ) {
builder.append(" extends " ).append(parentViewBinder).append( "<T>");
} else {
builder.append(" implements ViewBinder<T>" );
}
builder.append(" { \n") ;
emitBindMethod(builder) ;
builder.append( ‘\n‘ );
emitUnbindMethod(builder) ;
builder.append( "}\n" );
return builder.toString() ;
}
其实就是生成ViewBinder类,看下emitBindMethod方法:
private void emitBindMethod(StringBuilder builder) {
builder.append(" @Override " )
.append("public void bind(final Finder finder, final T target, Object source) { \n") ;
// Emit a call to the superclass binder, if any.
if (parentViewBinder != null) {
builder.append(" super.bind(finder, target, source); \n\n") ;
}
if (!viewIdMap.isEmpty() || ! collectionBindings.isEmpty()) {
// Local variable in which all views will be temporarily stored.
builder.append( " View view; \n") ;
// Loop over each view bindings and emit it.
for (ViewBindings bindings : viewIdMap.values()) {
emitViewBindings(builder, bindings);
}
// Loop over each collection binding and emit it.
for (Map.Entry<FieldCollectionViewBinding , int[]> entry : collectionBindings .entrySet()) {
emitCollectionBinding(builder, entry.getKey(), entry.getValue()) ;
}
}
if (!resourceBindings.isEmpty()) {
builder.append(" Resources res = finder.getContext(source).getResources(); \n") ;
for (FieldResourceBinding binding : resourceBindings) {
builder.append(" target." )
.append(binding.getName())
.append(" = res." )
.append(binding.getMethod())
.append(‘(‘ )
.append(binding.getId())
.append("); \n") ;
}
}
builder.append(" } \n") ;
}
利用上面解析保存的ViewBindings 来拼凑类。每次编译时ButterKnife都会针对你写的代码生成一个ViewBinder类,它里面有这样的代码:
public class MainActivity$$ViewBinder<T extends demo.lbb.test.MainActivity> implements ViewBinder<T> {
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131492969, "field ‘TextView‘ ");
target.textview= finder.castView(view, 2131492969, "field ‘imageview‘");
}
}
解析生成ViewHolder完成了,接下来看如何动态生成我们要的那些代码。
肯定是从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) ;
}
}
首先通过findViewBinderForClass来获得ViewHolder对象,然后调用ViewHolder的Binder来生成我们需要生成的代码。这里ViewHolder是什么鬼,我们先留个疑问在这。来看下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 ;
}
String clsName = cls.getName();
if (clsName.startsWith( ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) { //android. 和java.
if (debug) Log. d(TAG, "MISS: Reached framework class. Abandoning search." );
return NOP_VIEW_BINDER;
}
try {
Class<?> viewBindingClass = Class.forName (clsName + ButterKnifeProcessor.SUFFIX) ; //SUFFIX = "$$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()) ;
}
BINDERS .put(cls, viewBinder) ;//进行缓存
return viewBinder ;
}
通过BINDERS.get(cls)先判断缓存里是否存在,如果存在己经缓存的viewBinder对象就直接读取出来。不存在就Class.forName反射调用获取ViewBinder。findViewBinderForClass(cls.getSuperclass())用于在子类找不到继续找父类。
继续看 viewBinder.bind实现的功能。先看下它的参数Finder:
public enum Finder {
VIEW {
@Override protected View findView (Object source, int id) {
return ((View) source).findViewById(id) ;
}
@Override public Context getContext (Object source) {
return ((View) source).getContext() ;
}
},
ACTIVITY {
@Override protected View findView (Object source, int id) {
return ((Activity) source).findViewById(id) ;
}
@Override public Context getContext (Object source) {
return (Activity) source;
}
},
DIALOG {
@Override protected View findView (Object source, int id) {
return ((Dialog) source).findViewById(id) ;
}
@Override public Context getContext (Object source) {
return ((Dialog) source).getContext() ;
}
};
什么,一个枚举,什么return ((View) source).findViewById(id) ;不就是我们要生成的代码吗。到此我们可以猜测ButterKinfe根据传进来的context类型不同枚举生成不同的代码。是不是这样,往下看。上面说过我们生成了一个ViewHolder,ViewHolder.binder会调用以下代码:
view = finder.findRequiredView(source, 2131296337, "field ‘toolbar‘");
target.toolbar = finder.castView(view, 2131296337, "field ‘toolbar‘");
看下findRequiredView的具体实现:
public <T> T findRequiredView(Object source , int id, String who) {
T view = findOptionalView(source , id, who);
if (view == null) {
String name = getContext(source).getResources().getResourceEntryName(id);
throw new IllegalStateException( "Required view ‘"
+ name
+ "‘ with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add ‘@Nullable‘ annotation." );
}
return view;
}
通过findOptionView获得一个View,继续看看findOptionView的实现:
public <T> T findOptionalView(Object source, int id, String who) {
View view = findView(source, id);
return castView(view, id, who);
}
看到findView,是不是就明白了,通过Finder.findView()来获得要生成的代码,比如如果是Activity就调用((Activity) source).findViewById(id) ,到此绑定成功。
这篇博客只是简单分析下生成控件绑定代码的源码,其实ButterKnife能做的不仅如此,它还可以生成触发事件代码等,是一个非常好用的工具。
标签:
原文地址:http://blog.csdn.net/u014486880/article/details/51259659