bwin亚洲必赢5566手机版ButterKnife源码分析

前言

在N久以前,自从实验室里面包车型客车学长推荐自个儿用butterknife后,
从此的品种再也离不开butterknife了,可是自以为对它很熟时,前不久新浪实习生招聘二面却被面试官洗刷了一顿。然后全体二面完全是被虐的感觉到,猜测最终会挂,哎!
立刻被问到butterknife的落到实处,懵逼的本身不假思索就答上了诠释加反射。但是面试官却一脸猜忌的问小编:你规定?除了反射还有任何办法么????
自笔者了个去,难道butterknife不是用的反射??难道还有其余办法来兑现这厮儿么?不行,面试完了急迅clone
源码下来看看。不看不通晓,一看吓一跳,原来还真不是用的诠释加反射。在此多谢面试官为本人张开了新世界的大门,原来评释仍可以那样用!

Butterknife用法

自笔者相信学过android开发相应差不离都用过Butterknife呢,固然没用过也闻讯过啊?究竟是享誉的Jake
Wharton出品的事物。假如没用过的话能够看看这里,里面固然是讲的Annotation,可是例子正是用表明加反射达成的中低档的Butterknife。哈哈!用法里面大概也说了下。

Butterknife原理

讲到butterknife的规律。那里不得不提一下一般那种注入框架都以运转时注脚,即宣称注脚的生命周期为RUNTIME,然后在运维的时候经过反射实现注入,那种艺术固然简易,不过那种格局多多少少会有品质的费用。那么有没有一种办法能消除那种属性的损耗呢?
没错,答案肯定是一对,那正是Butterknife用的APT(Annotation Processing
Tool)编写翻译时解析技术。

APT大概就是您表明的笺注的生命周期为CLASS,然后继续AbstractProcessor类。继承这一个类后,在编写翻译的时候,编写翻译器会扫描全数带有你要拍卖的申明的类,然后再调用AbstractProcessor的process方法,对注明举行处理,那么我们就能够在拍卖的时候,动态变化绑定事件依旧控件的java代码,然后在运营的时候,直接调用bind方法成功绑定。
实际这种办法的功利是大家决不再三回壹处处写findViewById和onClick了,那个框架在编写翻译的时候帮大家自动生成了那几个代码,然后在运维的时候调用就行了。

源码解析

地点讲了那么多,其实都不如直接解析源码来得直接,上面大家就一步一步来探索大神怎么着完成Butterknife的吧。

获得源码的首先步是从我们调用的地点来突破,这大家就来看看程序里面是怎么调用它的吗?

 @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.setDebug(true);
    ButterKnife.bind(this);

    // Contrived code to use the bound fields.
    title.setText("Butter Knife");
    subtitle.setText("Field and method binding for Android views.");
    footer.setText("by Jake Wharton");
    hello.setText("Say Hello");

    adapter = new SimpleAdapter(this);
    listOfThings.setAdapter(adapter);
  }

上面是github上给的例证,大家平昔就从
ButterKnife.bind(this)出手吧,点进去看看:

  public static Unbinder bind(@NonNull Activity target) {
    return bind(target, target, Finder.ACTIVITY);
  }

咦?我再点:

  static Unbinder bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
    Class<?> targetClass = target.getClass();
    try {
      ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
      return viewBinder.bind(finder, target, source);
    } catch (Exception e) {
      throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
    }
  }

可以吗,bind方法首要正是获得大家绑定的Activity的Class,然后找到那些Class的ViewBinder,最终调用ViewBinder的bind()措施,那么难点来了,ViewBinder是个怎么着鬼???咱们开拓
findViewBinderForClass()方法。

 @NonNull
  private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
      throws IllegalAccessException, InstantiationException {
    ViewBinder<Object> viewBinder = BINDERS.get(cls);
    if (viewBinder != null) {
      return viewBinder;
    }
    String clsName = cls.getName();
    try {
      Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");
      viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
    } catch (ClassNotFoundException e) {
      viewBinder = findViewBinderForClass(cls.getSuperclass());
    }
    BINDERS.put(cls, viewBinder);
    return viewBinder;
  }

那里笔者去掉了部分Log音讯,保留了重点代码,上边的BINDE奥迪Q5S是2个保留了Class为key,Class$$ViewBinder为Value的贰个LinkedHashMap,主假诺做一下缓存,进步下次再来bind的习性。
在第柒行的时候,clsName
是大家传入要绑定的Activity类名,那里一定于得到了Activity$$ViewBinder其一东西,这几个类又是什么样玩意儿?其实从类名能够看出来,也正是Activity的3个里头类,那时候大家就要问了,大家在用的时候没有注明这几个类呀???从何地来的?
不要方,其实它就是大家在前头讲原理的时候说到的AbstractProcessor在编译的时候生成的1个类,大家前面再来看它,现在大家继续往上边分析。在第①1行就用反射反射了八个viewBinder
实例出来。
正好说了,那一个点子里面用linkhashMap做了下缓存,所以在15行的时候,就把刚刚反射的viewBinder作为value,Class作为key参与这么些LinkedHashMap,下次再bind那么些类的时候,就一向在第六行的时候取出来用,提高质量。

方今赶回刚刚的bind方法,大家得到了这一个Activity的viewBinder,然后调用它的bind方法。咦?那就完了???我们再点进viewBinder的bind方法看看。

public interface ViewBinder<T> {
  Unbinder bind(Finder finder, T target, Object source);
}

怎么样,接口???什么鬼?刚刚不是new了2个viewBinder出来么?然后那里就调用了那几个viewBinder的bind方法,
不行,小编要看一下bind到底是怎么样鬼!上边说了,Butterknife用了APT技术,那么那里的viewBinder应该正是编写翻译的时候生成的,那么我们就反编写翻译下apk。看看终归生成了何等代码:
上面大家就先用2个简易的绑定TextView的例证,然后反编写翻译出来看看:

public class MainActivity extends AppCompatActivity {

    @Bind(R.id.text_view)
    TextView textView;

    @OnClick(R.id.text_view)
     void onClick(View view) {
        textView.setText("我被click了");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        textView.setText("我还没有被click");
    }
}

源代码就那行几行,然后反编写翻译看看:

源代码就多了二个类,MainActivity$$ViewBinder,打开看看:

public class MainActivity$$ViewBinder<T extends MainActivity>
  implements ButterKnife.ViewBinder<T>
{
  public void bind(ButterKnife.Finder paramFinder, final T paramT, Object paramObject)
  {
    View localView = (View)paramFinder.findRequiredView(paramObject, 2131492944, "field 'textView' and method 'onClick'");
    paramT.textView = ((TextView)paramFinder.castView(localView, 2131492944, "field 'textView'"));
    localView.setOnClickListener(new DebouncingOnClickListener()
    {
      public void doClick(View paramAnonymousView)
      {
        paramT.onClick(paramAnonymousView);
      }
    });
  }

  public void unbind(T paramT)
  {
    paramT.textView = null;
  }
}

还记得刚刚说的,反射了三个Class$$ViewBinder么?看那里的类名。今后理应懂了啊?它恰恰也是实现了ButterKnife.ViewBinder<T>接口,我们说了,在bind方法中,最终调用了ViewBinder的bind方法,先说下多少个参数paramFinder其实就是二个Finder,因为我们能够在Activity中采纳butterknife,也得以在Fragment和Adapter等中采取butterknife,那么在分化的地点使用butterknife,这些Finder也就差异。在Activity中,其实源码
就是那样子的:

 ACTIVITY {
    @Override protected View findView(Object source, int id) {
      return ((Activity) source).findViewById(id);
    }

    @Override public Context getContext(Object source) {
      return (Activity) source;
    }
  }

有没有很熟识???其实依旧用的findViewById,那么在Dialog和Fragment中,遵照差其余地方,完结的方法各异。

那边的paramT和paramObject都是大家要绑定的Activity类,通过代码能够跟踪到。

回来上面包车型客车ViewBinder代码,首先调用了Finder的findRequiredView方法,其实那个方法最终通过处理即是调用了findView方法,得到对应的view,然后再赋值给paramT.textView,刚说了paramT就是老大要绑定的Activity,将来懂了啊?那里经过
paramT.textView
这样的调用格局,表明了Activity中不可能把TextView设置为private,不然会报错,其实那里能够用反射来获得textView的,那里大约也是为着品质着想吧。最终setOnClickListener,DebouncingOnClickListener本条Listener其实也是达成了View.OnClickListener
方法,然后在OnClick里面调用了doClick方法。流程大致跟踪了二遍。今后还预留最终一块了:

Butterknife到底是怎么着在编写翻译的时候生成代码的?

大家来看一下它的ButterKnifeProcessor类:

Init方法:

  @Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

    elementUtils = env.getElementUtils();
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
  }

ProcessingEnviroment参数提供不计其数实用的工具类Elements,
Types和Filer。Types是用来拍卖TypeMirror的工具类,Filer用来创设生成扶助文件。至于ElementUtils嘛,其实ButterKnifeProcessor在运作的时候,会扫描全体的Java源文件,然后每叁个Java源文件的每多少个片段都以一个Element,比如三个包、类依旧措施。

 @Override public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();

    types.add(BindArray.class.getCanonicalName());
    types.add(BindBitmap.class.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());
    types.add(BindView.class.getCanonicalName());
    types.add(BindViews.class.getCanonicalName());

    for (Class<? extends Annotation> listener : LISTENERS) {
      types.add(listener.getCanonicalName());
    }

    return types;
  }

getSupportedAnnotationTypes()方法首倘诺钦点ButterknifeProcessor是挂号给哪些评释的。大家能够看出,在源代码里面,作者3个二个地把Class文件加到那个LinkedHashSet里面,然后再把LISTENERubiconS也漫天加进去。

实际任何类最重庆大学的是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 {
        bindingClass.brewJava().writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
            e.getMessage());
      }
    }
    return true;
  }

本条格局的机能至关心注重假诺扫描、评估和拍卖大家先后中的注脚,然后生成Java文件。也正是日前说的ViewBinder。首先一进那些函数就调用了findAndParseTargets措施,大家就去探视findAndParseTargets情势到底做了哪些:

  private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

      // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      if (!SuperficialValidation.validateElement(element)) continue;
      try {
        parseBindView(element, targetClassMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

    Observable.from(topLevelClasses)
        .flatMap(new Func1<BindingClass, Observable<?>>() {
          @Override public Observable<?> call(BindingClass topLevelClass) {
            if (topLevelClass.hasViewBindings()) {
              // It has an unbinder class and it will also be the highest unbinder class for all
              // descendants.
              topLevelClass.setHighestUnbinderClassName(topLevelClass.getUnbinderClassName());
            } else {
              // No unbinder class, so null it out so we know we can just return the NOP unbinder.
              topLevelClass.setUnbinderClassName(null);
            }

            // Recursively set up parent unbinding relationships on all its descendants.
            return ButterKnifeProcessor.this.setParentUnbindingRelationships(
                topLevelClass.getDescendants());
          }
        })
        .toCompletable()
        .await();

    return targetClassMap;
  }

此地代码炒鸡多,作者就不整敬服出来了,只贴出来一部分,那些主意最后还用了rxjava的规范。那么些主意的严重性的流程如下:

  • 扫描全体具有证明的类,然后依照那个类的音信生成BindingClass,末了生成以TypeElement为键,BindingClass为值的键值对。
  • 循环遍历那么些键值对,依据TypeElement和BindingClass里面包车型地铁音信变化对应的java类。例如AnnotationActivity生成的类即为Cliass$$ViewBinder类。

因为大家事先用的例证是绑定的八个View,所以我们就只贴精通析View的代码。好吧,那里遍历了装有带有@BindView的Element,然后对每四个Element实行辨析,也就进去了parseBindView这一个主意中:

private void parseBindView(Element element, Map<TypeElement, BindingClass> targetClassMap,
      Set<TypeElement> erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Start by verifying common generated code restrictions.
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);

    // 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)",
          BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    if (hasError) {
      return;
    }

    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();

    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)",
              BindView.class.getSimpleName(), id, existingBinding.getName(),
              enclosingElement.getQualifiedName(), element.getSimpleName());
          return;
        }
      }
    } else {
      bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    }

    String name = element.getSimpleName().toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(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);
  }

然后那里从一进去这些艺术到

  int id = element.getAnnotation(BindView.class).value();

都以在得到评释新闻,然后验证注脚的target的类型是还是不是继续自view,然后上边这一行代码得到我们要绑定的View的id,再从targetClassMap里面取出BindingClass(这一个BindingClass是治本了独具有关那几个注脚的一些新闻还有实例自个儿的消息,其实最后是经过BindingClass来生成java代码的),假诺targetClassMap里面不设有的话,就在

      bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);

那边生成1个,大家进来看一下getOrCreateTargetClass

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);
      boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
      String className = getClassName(enclosingElement, classPackage) + BINDING_CLASS_SUFFIX;
      String classFqcn = getFqcn(enclosingElement) + BINDING_CLASS_SUFFIX;

      bindingClass = new BindingClass(classPackage, className, isFinal, targetType, classFqcn);
      targetClassMap.put(enclosingElement, bindingClass);
    }
    return bindingClass;
  }

这中间其实相当粗略,便是获得一些那些申明所修饰的变量的局地音信,比如类名呀,包名呀,然后className此处就赋值成Class$$ViewHolder了,因为:

  private static final String BINDING_CLASS_SUFFIX = "$$ViewBinder";

下一场把这一个分析后的bindingClass参预到targetClassMap里面。

重返刚刚的parseBindView中,依照view的新闻生成三个FieldViewBinding,最终添加到上边生成的BindingClass实例中。那里基本形成了剖析工作。最终回到findAndParseTargets中:

Observable.from(topLevelClasses)
        .flatMap(new Func1<BindingClass, Observable<?>>() {
          @Override public Observable<?> call(BindingClass topLevelClass) {
            if (topLevelClass.hasViewBindings()) {
              topLevelClass.setHighestUnbinderClassName(topLevelClass.getUnbinderClassName());
            } else {

              topLevelClass.setUnbinderClassName(null);
            }
            return ButterKnifeProcessor.this.setParentUnbindingRelationships(
                topLevelClass.getDescendants());
          }
        })
        .toCompletable()
        .await();

此间运用了rxjava,其实那里关键的行事是树立方面包车型大巴绑定的具有的实例的解绑的涉嫌,因为大家绑定了,最终在代码中依旧会解绑的。那里预先处理好了这一个关系。因为此地要递归地形成解绑,所以用了flatmap,flatmap把每3个开立出来的
Observable 发送的轩然大波,都集中到同3个 Observable 中,然后这些 Observable
负责将这一个事件联合交由 Subscriber 。
只是这有的关联到很多rxjava的事物,有趣味的童鞋去探视大神的写给android开发者的奔驰M级xJava
详解
那篇文章,然后再来看那里就很轻松了。

回到大家的process中,
未来解析完了annotation,该生成java文件了,小编再把代码贴一下:

 @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 {
        bindingClass.brewJava().writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
            e.getMessage());
      }
    }

    return true;
  }

遍历刚刚获得的targetClassMap ,然后再二个一个地经过

bindingClass.brewJava().writeTo(filer);

来生成java文件。可是生成的java文件也是根据地点的音信来用字符串拼接起来的,但是那个工作在brewJava()中成就了:

  JavaFile brewJava() {
    TypeSpec.Builder result = TypeSpec.classBuilder(className)
        .addModifiers(PUBLIC)
        .addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));
    if (isFinal) {
      result.addModifiers(Modifier.FINAL);
    }

    if (hasParentBinding()) {
      result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentBinding.classFqcn),
          TypeVariableName.get("T")));
    } else {
      result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
    }

    result.addMethod(createBindMethod());

    if (hasUnbinder() && hasViewBindings()) {
      // Create unbinding class.
      result.addType(createUnbinderClass());

      if (!isFinal) {
        // Now we need to provide child classes to access and override unbinder implementations.
        createUnbinderCreateUnbinderMethod(result);
      }
    }

    return JavaFile.builder(classPackage, result.build())
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

此间运用了java中的javapoet技术,不明白的童鞋能够传递到github上面,也是square的名篇,那一个不在那篇小说的讲授范围内,有趣味的童鞋可以去探访,很科学的开源项目。

最后通过writeTo(Filer filer)生成java源文件。

即使对javapoet感兴趣的话,能够看看那篇文章,介绍javapoet的。

写了有些个时辰终于写完了。假设有荒唐欢迎指正(๑>؂<๑)