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消息,保留了关键代码,下边的BINDERS是一个保留了Class为key,Class$$ViewBinder啊Value的一个LinkedHashMap,重倘诺召开一下缓存,进步下次重来bind的特性。
每当第10实施之时光,clsName
是咱们传入要绑定的Activity类名,这里十分给以到了Activity$$ViewBinder斯事物,那一个仿佛以是啊玩意儿?其实自从类名可以关押出来,相当于Activity的一个里类,这时候我们即便使问了,我们在用之上从不阐明是仿佛呀???从何来的?
不要方,其实她就是咱以事先说原理的时候说交的AbstractProcessor在编译的时节其余一个接近,我们后更来拘禁其,现在大家累朝着下分析。在第11实施就是因而反射反射了一个viewBinder
实例出来。
恰说了,这些办法中所以linkhashMap做了下缓存,所以于15推行之时节,就拿刚映的viewBinder作为value,Class作为key参预这LinkedHashMap,下次再bind这多少个仿佛的早晚,就一向当第4实施的当儿获得下用,进步性。

今回来刚刚的bind方法,大家将到了之Activity的viewBinder,然后调用它的bind方法。咦?这虽了了???大家再一次接触进viewBinder的bind方法看看。

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

好家伙,接口???什么鬼?刚刚不是new了一个viewBinder出来么?然后这里就是调用了之viewBinder的bind方法,
不行,我要扣一下bind究竟是呀破!下边说了,Butterknife用了APT技术,那么这里的viewBinder应该就是是编译的早晚其它,那么我们就是反而编译下apk。看看究竟好成了呀代码:
下我们就先用一个略的绑定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是登记为哪声明的。大家得以看来,在源代码里面,作者一个一个地管Class文件加到那多少个LinkedHashSet里面,然后再将LISTENERS也漫天加进去。

实则整个类最要紧之凡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);

此间分外成一个,我们上看一下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的音异常成一个菲尔德(Field)ViewBinding,最后补充加到下边生成的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把各一个创出来的
Observable 发送的轩然大波,都汇集到和一个 Observable 中,然后这些 Observable
负责将这么些事件联合交由 Subscriber 。
只是就片事关到大多rxjava的东西,有趣味的童鞋去探望大神的描摹于android开发者的RxJava
详解
即时首著作,然后重新来拘禁这里虽杀轻松了。

回到我们的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的。

描绘了少数独钟头终于写了了。固然起误欢迎指正(๑>؂<๑)