ButterKnife第三方库源码分析
2016-11-09 23:42
197 查看
ButterKnife原理其实也很简单
我的Github博客详解
ButterKnife是大名鼎鼎JakeWharton热门开源项目的其中一个,让开发者不再重复的进行findViewById的操作。
配合android studio的插件,一键自动生成xml文件所有view的实例。
ButterKnife为什么会那么神奇,自动帮助开发者省去了繁琐的操作,他的实现的原理到底是怎么样的呢?
下面我们从代码使用上,一步一步的分析ButterKnife的实现原理
分析ButterKnife版本:com.jakewharton:butterknife:7.0.1
使用ButterKnife快速初始化xml布局对象.
一段很常规使用ButterKnife快速初始化xml布局对象的代码。为什么当onCreate方法的ButterKnife.bind(this);
调用完毕,xml所有的布局对象都初始化好了呢?
我们进入到@Bind注解里看看究竟,看看是否找得到线索
可以看到@Retention(CLASS),这句表示:保留时间 编译时,也就是工程编译时运行的注解.
常规获取View对象的方式是这样的:
所以说,ButterKnife这个库应该拿到了R.id.tv01 这个id值,也拿到了tv01成员变量,在通过findViewById给tv01赋值
但是,ButterKnife在哪里进行这样的操作呢?
从@Bind注解来看,应该是编译时拿到了id值
我们在build目录下找到了ButterKnife生成的新文件:
build\generated\source\apt\debug\com\butterknifedemo\MainActivity$$ViewBinder
我们发现ButterKnife在build目录下生成了一个类,这个类竟然帮助我们完成了findVieweById的操作
那这个类是怎么制作出来的呢?
现在,我们直接去看ButterKnife源码:
这里有两个问题:
1. findViewBinderForClass通过Activity字节码如何找到的viewBinder?
2. viewBinder是什么?
首先,看viewBinder是什么:
原来是个接口,注释说自动生成代码用的,看看刚刚在build找到的类:
原来viewBinder.bind(finder, target, source);这行代码调用了MainActivity$$ViewBinder类里的bind方法,
帮助我们完成findViewById工作
现在我们知道了,平时我们调用ButterKnife.bind(this);
最终都会调用对应生成的$$ViewBinder类里的bind方法帮助我们完成繁琐的操作
问题2:怎么通过Activity字节码找到viewBinder对象的?
原来先从BINDERS.get(cls);里面取,空的话在通过Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
创建出一个新对象出来,前提是这个$$ViewBinder已经生成好了
什么时候生成这个类,怎么生成的?通过注解@Bind我们应该猜到,是工程编译时就生成好了的
在ButterKnife源码中我们发现了这一个类:
extends AbstractProcessor,继承这一个类,表示它可以在工程编译是运行里面的process方法,
ButterKnife就是通过编译时,apt会自动查找集成AbstractProcessor的类,调用process方法
在process方法中找到存在ButterKnife的注解信息,获取在注解对应下的数据,例如id值
上面的代码我们还可以看到,Bind,OnClick等等注解已经存储好了,就等着遍历配对处理获取数据
看process方法:
从JavaFileObject,Writer这个类就可以知道,ButterKnife把一些东西写到文件中去了,应该猜到
那些自动生成的java文件就从这里出来的
代码生成java文件,代码加载java文件去运行,有点意思
先看看findAndParseTargets方法做了什么:
可以看到,正在查找Bind之类的注解,猜都猜到通过定位注解获取注解下面的值了吧
现在,我们回过头看看把什么东西写到文件中去了,看着行代码: writer.write(bindingClass.brewJava());
可以一目了然的看到使用了StringBuilder手动拼接字符串的方式,生成了java文件,挺不容易的。
问题又来了,这些java文件静态不变化的部分可以写死,那些动态灵活的部分呢?例如View的id,对象名称
继续深入看源码:
从上面的代码可以知道,动态的部分通过BindingClass这个类来获取的,那这是类怎么来的,看之前的代码:
原来这个类是从findAndParseTargets里来的,刚刚我们知道,里面做了定位Bind,OnClick注解的操作,
定位的同时也把注解的值,例如id值,变量名称存在到BindingClass对象中了,很符合面向对象的思想
最后,还有一点的是:ButterKnife通过注解获取id值并没有使用到反射,获取到变量也是通过Activity.view的形式
不同与一些反射获取注解的框架,使用反射会增加IO操作,增加了时间操作,多了会变得卡顿
反射的方式成员变量可使用private,而ButterKnife不可以,必须public或者protected
因为ButterKnife没有使用反射,需要Activity.view这样去获取一些对象赋值
具体可以去看源码,总的来说:
1, ButterKnife将View的id值放到@Bind注解中
2, ButterKnife通过extends AbstractProcessor编译时自动调用process方法来定位和存在注解与注解上的id值
3, 找到所有带注解与值的对象,存储在集合中,一个for循环一顿狂写,把java文件写到build目录下
4, 当调用ButterKnife.bind(this)的时候,最终会调用生成的viewBinder类里的bind方法
5, viewBinder里的bind方法,找已动态生成好了finfViewById的过程,通过Activity.view的形式初始化所有view
分析就到这里了
11/9/2016 11:33:29 PM
我的Github博客详解
ButterKnife是大名鼎鼎JakeWharton热门开源项目的其中一个,让开发者不再重复的进行findViewById的操作。
配合android studio的插件,一键自动生成xml文件所有view的实例。
ButterKnife为什么会那么神奇,自动帮助开发者省去了繁琐的操作,他的实现的原理到底是怎么样的呢?
下面我们从代码使用上,一步一步的分析ButterKnife的实现原理
分析ButterKnife版本:com.jakewharton:butterknife:7.0.1
使用ButterKnife快速初始化xml布局对象.
public class MainActivity extends AppCompatActivity { @Bind(R.id.tv01) TextView tv01; @Bind(R.id.tv02) TextView tv02; @Bind(R.id.tv03) TextView tv03; @Bind(R.id.tv04) TextView tv04; @Bind(R.id.activity_main) LinearLayout activityMain; @OnClick(R.id.tv01) public void test(View v) { } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); } }
一段很常规使用ButterKnife快速初始化xml布局对象的代码。为什么当onCreate方法的ButterKnife.bind(this);
调用完毕,xml所有的布局对象都初始化好了呢?
我们进入到@Bind注解里看看究竟,看看是否找得到线索
@Retention(CLASS) @Target(FIELD) public @interface Bind { /** View ID to which the field will be bound. */ int[] value(); }
可以看到@Retention(CLASS),这句表示:保留时间 编译时,也就是工程编译时运行的注解.
常规获取View对象的方式是这样的:
tv01 = (TextView) findViewById(R.id.tv01); //现在变成这样 @Bind(R.id.tv01) TextView tv01;
所以说,ButterKnife这个库应该拿到了R.id.tv01 这个id值,也拿到了tv01成员变量,在通过findViewById给tv01赋值
但是,ButterKnife在哪里进行这样的操作呢?
从@Bind注解来看,应该是编译时拿到了id值
我们在build目录下找到了ButterKnife生成的新文件:
build\generated\source\apt\debug\com\butterknifedemo\MainActivity$$ViewBinder
// Generated code from Butter Knife. Do not modify! package com.butterknifedemo; import android.view.View; import butterknife.ButterKnife.Finder; import butterknife.ButterKnife.ViewBinder; public class MainActivity$$ViewBinder<T extends com.butterknifedemo.MainActivity> implements ViewBinder<T> { @Override public void bind(final Finder finder, final T target, Object source) { View view; view = finder.findRequiredView(source, 2131427413, "field 'tv01' and method 'test'"); target.tv01 = finder.castView(view, 2131427413, "field 'tv01'"); view.setOnClickListener( new butterknife.internal.DebouncingOnClickListener() { @Override public void doClick( android.view.View p0 ) { target.test(p0); } }); view = finder.findRequiredView(source, 2131427414, "field 'tv02'"); target.tv02 = finder.castView(view, 2131427414, "field 'tv02'"); view = finder.findRequiredView(source, 2131427415, "field 'tv03'"); target.tv03 = finder.castView(view, 2131427415, "field 'tv03'"); view = finder.findRequiredView(source, 2131427416, "field 'tv04'"); target.tv04 = finder.castView(view, 2131427416, "field 'tv04'"); view = finder.findRequiredView(source, 2131427412, "field 'activityMain'"); target.activityMain = finder.castView(view, 2131427412, "field 'activityMain'"); } @Override public void unbind(T target) { target.tv01 = null; target.tv02 = null; target.tv03 = null; target.tv04 = null; target.activityMain = null; } }
我们发现ButterKnife在build目录下生成了一个类,这个类竟然帮助我们完成了findVieweById的操作
那这个类是怎么制作出来的呢?
现在,我们直接去看ButterKnife源码:
//先进入里面看看 ButterKnife.bind(this); //显然Activity对象作为target往下传递了 //Finder.ACTIVITY 是什么呢 public static void bind(Activity target) { bind(target, target, Finder.ACTIVITY); } //Finder.ACTIVITY 原来是 ButterKnife 内部枚举 //return ((Activity) source).findViewById(id); 注意看句代码 public final class ButterKnife { private ButterKnife() { throw new AssertionError("No instances."); } /** DO NOT USE: Exposed for generated code. */ @SuppressWarnings("UnusedDeclaration") // Used by generated code. 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(); } }; //findViewBinderForClass这个方法通过Activity对象去查找返回了一个ViewBinder类, //然后viewBinder.bind(finder, target, source); 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); } }
这里有两个问题:
1. findViewBinderForClass通过Activity字节码如何找到的viewBinder?
2. viewBinder是什么?
首先,看viewBinder是什么:
/** DO NOT USE: Exposed for generated code. */ public interface ViewBinder<T> { void bind(Finder finder, T target, Object source); void unbind(T target); }
原来是个接口,注释说自动生成代码用的,看看刚刚在build找到的类:
public class MainActivity$$ViewBinder <T extends com.butterknifedemo.MainActivity> implements ViewBinder<T> { @Override public void bind(final Finder finder, final T target, Object source) {
原来viewBinder.bind(finder, target, source);这行代码调用了MainActivity$$ViewBinder类里的bind方法,
帮助我们完成findViewById工作
现在我们知道了,平时我们调用ButterKnife.bind(this);
最终都会调用对应生成的$$ViewBinder类里的bind方法帮助我们完成繁琐的操作
问题2:怎么通过Activity字节码找到viewBinder对象的?
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)) { 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);里面取,空的话在通过Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
创建出一个新对象出来,前提是这个$$ViewBinder已经生成好了
什么时候生成这个类,怎么生成的?通过注解@Bind我们应该猜到,是工程编译时就生成好了的
在ButterKnife源码中我们发现了这一个类:
public final class ButterKnifeProcessor extends AbstractProcessor { public static final String SUFFIX = "$$ViewBinder"; public static final String ANDROID_PREFIX = "android."; public static final String JAVA_PREFIX = "java."; static final String VIEW_TYPE = "android.view.View"; private static final String COLOR_STATE_LIST_TYPE = "android.content.res.ColorStateList"; private static final String DRAWABLE_TYPE = "android.graphics.drawable.Drawable"; private static final String NULLABLE_ANNOTATION_NAME = "Nullable"; private static final String ITERABLE_TYPE = "java.lang.Iterable<?>"; private static final String LIST_TYPE = List.class.getCanonicalName(); 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 // ); @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; }
extends AbstractProcessor,继承这一个类,表示它可以在工程编译是运行里面的process方法,
ButterKnife就是通过编译时,apt会自动查找集成AbstractProcessor的类,调用process方法
在process方法中找到存在ButterKnife的注解信息,获取在注解对应下的数据,例如id值
上面的代码我们还可以看到,Bind,OnClick等等注解已经存储好了,就等着遍历配对处理获取数据
看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(); 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; }
从JavaFileObject,Writer这个类就可以知道,ButterKnife把一些东西写到文件中去了,应该猜到
那些自动生成的java文件就从这里出来的
代码生成java文件,代码加载java文件去运行,有点意思
先看看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); } } // 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); } } ....
可以看到,正在查找Bind之类的注解,猜都猜到通过定位注解获取注解下面的值了吧
现在,我们回过头看看把什么东西写到文件中去了,看着行代码: writer.write(bindingClass.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(); }
可以一目了然的看到使用了StringBuilder手动拼接字符串的方式,生成了java文件,挺不容易的。
问题又来了,这些java文件静态不变化的部分可以写死,那些动态灵活的部分呢?例如View的id,对象名称
继续深入看源码:
//bind方法代码拼接 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); } private void emitViewBindings(StringBuilder builder, ViewBindings bindings) { builder.append(" view = "); List<ViewBinding> requiredViewBindings = bindings.getRequiredBindings(); if (requiredViewBindings.isEmpty()) { builder.append("finder.findOptionalView(source, ") .append(bindings.getId())//这个就是View的id了 .append(", null);\n"); } else { if (bindings.getId() == View.NO_ID) { builder.append("target;\n"); } else { builder.append("finder.findRequiredView(source, ") .append(bindings.getId()) .append(", \""); emitHumanDescription(builder, requiredViewBindings); builder.append("\");\n"); } } //字段看这里,进去 emitFieldBindings(builder, bindings); emitMethodBindings(builder, bindings); } static void emitHumanDescription(StringBuilder builder, Collection<? extends ViewBinding> bindings) { Iterator<? extends ViewBinding> iterator = bindings.iterator(); switch (bindings.size()) { case 1: builder.append(iterator.next().getDescription());//View变量名称 break; case 2: builder.append(iterator.next().getDescription()) @Override public String getDescription() { return "field '" + name + "'"; }
从上面的代码可以知道,动态的部分通过BindingClass这个类来获取的,那这是类怎么来的,看之前的代码:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) { Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
原来这个类是从findAndParseTargets里来的,刚刚我们知道,里面做了定位Bind,OnClick注解的操作,
定位的同时也把注解的值,例如id值,变量名称存在到BindingClass对象中了,很符合面向对象的思想
private void parseBindOne(Element element, Map<TypeElement, BindingClass> targetClassMap, Set<String> erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); ... 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()); } 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; }
最后,还有一点的是:ButterKnife通过注解获取id值并没有使用到反射,获取到变量也是通过Activity.view的形式
不同与一些反射获取注解的框架,使用反射会增加IO操作,增加了时间操作,多了会变得卡顿
反射的方式成员变量可使用private,而ButterKnife不可以,必须public或者protected
因为ButterKnife没有使用反射,需要Activity.view这样去获取一些对象赋值
具体可以去看源码,总的来说:
1, ButterKnife将View的id值放到@Bind注解中
2, ButterKnife通过extends AbstractProcessor编译时自动调用process方法来定位和存在注解与注解上的id值
3, 找到所有带注解与值的对象,存储在集合中,一个for循环一顿狂写,把java文件写到build目录下
4, 当调用ButterKnife.bind(this)的时候,最终会调用生成的viewBinder类里的bind方法
5, viewBinder里的bind方法,找已动态生成好了finfViewById的过程,通过Activity.view的形式初始化所有view
分析就到这里了
11/9/2016 11:33:29 PM
相关文章推荐
- MBProgressHUD第三方库源码分析
- 【原创】k8s源码分析------第三方库go-restful分析
- 微信第三方服务平台源码分析——每个Action与模块的对应关系
- ButterKnife源码分析及简单实现
- 【原创】k8s源码分析------第三方库etcd client分析
- Android Butterknife 框架源码解析(3)——Butterknife 8.7.0源码分析
- Android UI注解框架 ButterKnife源码及原理分析
- butterknife源码分析:谈一谈Java的注解
- 第三方开源库OKHttp-整体架构和源码分析
- ButterKnife -- 源码分析
- 对MBProgressHUD第三方进行源码分析
- zookeeper实战与源码分析----第三方客户端ZkClient使用之监听器
- 第三方开源库 RxJava - 基本使用和源码分析
- 第三方开源库 Retrofit - 源码设计模式分析
- 对MBProgressHUD第三方进行源码分析
- 第三方开源库 EventBus - 源码分析和手写
- owncloud源码分析3--第三方类库
- 我所用到的第三方开源库源码阅读分析笔记
- butterknife 源码分析
- butterknife 源码分析