ButterKnife源码分析。ButterKnife源码分析。

前言

以N久之前,自从实验室中的学长推荐自己所以butterknife后,
从此的种还为相差不起来butterknife了,然而自以为对它杀熟时,前不久今条修实习生招聘二面却吃面试官洗刷了千篇一律抛锚。然后所有二面完全是被虐的感到,估计最后见面挂,哎!
当时被咨询到butterknife的落实,懵逼的自己想都不想就报上了诠释加映。然而面试官却一如既往脸狐疑的咨询我:你规定?除了反射还出其它办法吗????
自了个去,难道butterknife不是用的反射??难道还生其它办法来落实就玩意儿儿么?不行,面试结束了赶快clone
源码下来看看。不看无了解,一看吓一跨,原来还真不是故底诠释加映。在斯感谢面试官为己敞开了初世界之大门,原来注解还会如此用!

butterknife注解框架相信广大同室都以用,但是你真的了解她的兑现原理也?那么今天于咱们来探视它究竟是怎么落实之(注:本文是基于butterknife:8.5.1版本进行分析)。

Butterknife用法

自家相信学过android开发相应多还因此过Butterknife吧,就算没因此过吧闻讯了吧?毕竟是举世闻名的Jake
Wharton出品的物。要是没有因此了之口舌可看看这里,里面虽然是道的Annotation,但是例子就是用注解加映实现之初级的Butterknife。哈哈!用法里大概为说了产。

前言

优先来探望有备知识

Butterknife原理

摆到butterknife的法则。这里不得不提一下貌似这种注入框架还是运行时注解,即宣称注解的生命周期为RUNTIME,然后于运作的时候经过反射完成注入,这种办法虽然简易,但是这种措施多多少少会发生性的吃。那么来无来一样栽艺术能够化解这种属性的损耗呢?
没错,答案肯定是局部,那就算是Butterknife用的APT(Annotation Processing
Tool)编译时解析技术。

APT大概就是您声明的注解的生命周期为CLASS,然后继续AbstractProcessor类。继承这看似后,在编译的当儿,编译器会扫描所有带有你而处理的注解的切近,然后再调用AbstractProcessor的process方法,对注解进行处理,那么我们即便好在拍卖的时段,动态变化绑定事件或控件的java代码,然后于运行的时节,直接调用bind方法好绑定。
实质上这种方式的功利是咱们不要再同遍一律遍地写findViewById和onClick了,这个框架在编译的上拉咱自动生成了这些代码,然后在运转的下调用就推行了。

java注解

java有三类注解,通过长注解@Retention来标识:

  • RetentionPolicy.SOURCE:源码级别解析,例如@Override,@SupportWarnngs,这里注解在编译成功后即使不会见再从作用,并且不见面冒出在.class中。

  • RetentionPolicy.CLASS:编译时分析,默认的分析方法,会保留在终极之class中,但无法再运行时取得。

  • RetentionPolicy.RUNTIME:运行时注解,会保留在最终之class中,这看似注解可以为此反射API中getAnnotations()获取到。

源码解析

地方讲了那基本上,其实还不如直接解析源码来得直接,下面我们虽同一步一步来探索大神怎样实现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主意。流程大概跟了同样全勤。现在尚预留最后一片了:

编译时注解

编译时注解是注解强大的地方之一,你可用它来提携您生成java代码去处理局部逻辑,避免了于是反射解析所带来的性的开发。ButterKnife的核心思想正是因此编译时注解。

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的音大成一个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把各级一个创造出来的
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的。

形容了好几个钟头终于写了了。如果有不当欢迎指正(๑>؂<๑)

ButterKnife解析

从Bind开始

明显,使用butterknife时第一用在Activity#onCreate方法被添加这么一句代码

ButterKnife.bind(this);

那么我们就算后方法入手,以下是Butterknife.bind()的源码:

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
}

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    try {
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
     //...... 省略
    }
}

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
      return bindingCtor;
    }
    String clsName = cls.getName();
    //...... 省略
    try {
      Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);

    } catch (ClassNotFoundException e) {
      //...... 省略
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
}

先是,bind方法吃调用了createBinding(…)方法,而在createBinding()方法中取了一个Unbinder的构造器,并实例化此类。可以发现是Unbinder的构造器是经过findBindingConstructorForClass这个法子取得的,那就是进来这个方吃见。可以观看这道被率先从BINDINGS中得到unbinder类构造器,如果部分言语一直回到,没有则通过反射得到此类构造器,先在BINDINGS中存放一份,然后于拿其回来。其实这里BINDINGS的意图是缓存通过反射得到的类。避免频繁通过反射获取所带来的性开销。

bind方法暂时就分析到这里,其实如果是查看ButterKnife这个类会发现,bind方法有多重载方法,其中有指向View的,有针对Dialog的等等作用还一样,就是若利用传入的target来实例化一个类名为target_ViewBinding类的对象。那么target_ViewBinding这个近乎是自从哪里来之吗?作用来是什么吗?

@BindView(id)注解

使butterknife的功利是不要程序员手动findViewById去追寻对应之View,而独自待加一个@BindView(id)即可,那么butterknife是怎么通过@BindView注解来将相应之View初始化的吗?

为咱先行来看望这注解的代码:

@Retention(CLASS) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

透过上述代码可以见到,BindView注解是一个编译时注解(@Retention(CLASS)),并且不得不作用在性质上(@Target(FIELD))。

倘你针对java注解有一定之刺探,那尔不怕必将知道,编译时注解最终都是有AbstractProcessor的子类来拍卖的,那我们不怕找到相应之类为ButterKnifeProcessor类。

先来探视process方法:

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      JavaFile javaFile = binding.brewJava(sdk);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
  }

这个方法发生点儿单作用,第一凡分析所有注解findAndParseTargets(env),第二是生成java代码。

  1. 浅析所有注解findAndParseTargets(env)

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    //省略代码...
    // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      try {
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
   //省略代码...
    return bindingMap;
}

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

    //省略代码 验证 1、被注解的属性不能是private或static。2、验证被注解的属性必须是View的子类

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

    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder != null) {
      String existingBindingName = builder.findExistingBindingName(getId(id));
      if (existingBindingName != null) {
        error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
            BindView.class.getSimpleName(), id, existingBindingName,
            enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    } else {
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }

    String name = element.getSimpleName().toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);

    //将被注解的属性封装成FieldViewBinding存储在buildSet中
    builder.addField(getId(id), new FieldViewBinding(name, type, required));

    // 将绑定变量所在的类添加到待unBind序列中。
    erasedTargetNames.add(enclosingElement);
  }

当下有的代码很易理解,解析所有为@BindView所注解的Element(肯定是Field),并处理它。具体分析代码在parseBindVIew方法中。首先校验属性的合法性:

  • 叫诠释的习性不可知是private或static
  • 为诠释的属性必须是View的子类
  • 特性和id必须逐项对应

辅助是拿性能封装成FieldViewBinding对象,存储于BindingSet中。那么BindingSet又是单什么东西呢?

static final class Builder {
    private final TypeName targetTypeName;
    private final ClassName bindingClassName;
    private BindingSet parentBinding;
    private final Map<Id, ViewBinding.Builder> viewIdMap = new LinkedHashMap<>();
    //省略代码
    void addField(Id id, FieldViewBinding binding) {
      getOrCreateViewBindings(id).setFieldBinding(binding);
    }

    private ViewBinding.Builder getOrCreateViewBindings(Id id) {
      ViewBinding.Builder viewId = viewIdMap.get(id);
      if (viewId == null) {
        viewId = new ViewBinding.Builder(id);
        viewIdMap.put(id, viewId);
      }
      return viewId;
    }

    BindingSet build() {
      //省略代码      
      return new BindingSet(targetTypeName, bindingClassName, isFinal, isView, isActivity, isDialog,
          viewBindings.build(), collectionBindings.build(), resourceBindings.build(),
          parentBinding);
    }
  }

此贴出来了BindingSet的结构器Builder,可以看到此类包了给诠释的近乎的音讯及注解的有所的特性,viewIdMap中蕴藏了颇具的笺注字段,这些字段都受封装成ViewBinding(其实就是是用id和总体性名包装成一个靶)。最后由BindingSet负责生成java代码。

static Builder newBuilder(TypeElement enclosingElement) {
    //省略代码
    String packageName = getPackage(enclosingElement).getQualifiedName().toString();
    String className = enclosingElement.getQualifiedName().toString().substring(
        packageName.length() + 1).replace('.', '$');
    ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");

    boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}

可观看构造Builder时会传入包名,类名,而此类名
className_ViewBinding就是最后给转移的类的称呼。还记在bind方法中见面透过反射去实例化一个target_ViewBinding类的靶子吧,这个看似其实就算是这里通过注解来自动生成的。到此地butterknife的原理就是分析了了,下面我们来探望如何生成java代码。

2.使用javapoet生成java代码

再次介绍一个大杀器
javapoet,square出品的生成.java文件之Java
API。通过清晰的语法来大成一个文书结构,无敌。
这就是说我们返回process方法吃查阅。

JavaFile javaFile = binding.brewJava(sdk);
javaFile.writeTo(filer);

这边要逻辑在binding.brewJava()方法中。

JavaFile brewJava(int sdk) {
    return JavaFile.builder(bindingClassName.packageName(), createType(sdk))
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }

此间builder的参数第一个凡是包名。第二单凡是TypeSpec对象,就是只要变的好像的音。

private TypeSpec createType(int sdk) {

    //获取一个builder对象,将类的修饰符设置为public,如果允许是final时,设置为final
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC);
    if (isFinal) {
      result.addModifiers(FINAL);
    }

    ......
    //添加属性
    if (hasTargetField()) {
      result.addField(targetTypeName, "target", PRIVATE);
    }

    //添加构造器
    if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) {
      result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {
      result.addMethod(createBindingConstructorForDialog());
    }
    if (!constructorNeedsView()) {
      // Add a delegating constructor with a target type + view signature for reflective use.
      result.addMethod(createBindingViewDelegateConstructor());
    }
    result.addMethod(createBindingConstructor(sdk));

    //添加一个unbind方法
    if (hasViewBindings() || parentBinding == null) {
      result.addMethod(createBindingUnbindMethod(result));
    }

    return result.build();
  }

当此艺术被好看看最后生成的接近的连带信息,最着重的给属性初始化的代码是当createBindingConstructor(sdk)
这个办法中。

private MethodSpec createBindingConstructor(int sdk) {
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC);
    ......
    //绑定View属性
    if (hasViewBindings()) {
      ......
      for (ViewBinding binding : viewBindings) {
        addViewBinding(constructor, binding);
      }
      ......
    }
    ......
    return constructor.build();
  }

  private void addViewBinding(MethodSpec.Builder result, ViewBinding binding) {
    if (binding.isSingleFieldBinding()) {
      // Optimize the common case where there's a single binding directly to a field.
      FieldViewBinding fieldBinding = binding.getFieldBinding();
      CodeBlock.Builder builder = CodeBlock.builder()
          .add("target.$L = ", fieldBinding.getName());

      boolean requiresCast = requiresCast(fieldBinding.getType());
      if (!requiresCast && !fieldBinding.isRequired()) {
        builder.add("source.findViewById($L)", binding.getId().code);
      } else {
        builder.add("$T.find", UTILS);
        builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView");
        if (requiresCast) {
          builder.add("AsType");
        }
        builder.add("(source, $L", binding.getId().code);
        if (fieldBinding.isRequired() || requiresCast) {
          builder.add(", $S", asHumanDescription(singletonList(fieldBinding)));
        }
        if (requiresCast) {
          builder.add(", $T.class", fieldBinding.getRawType());
        }
        builder.add(")");
      }
      result.addStatement("$L", builder.build());
      return;
    }

    ......

    addFieldBinding(result, binding);
    addMethodBindings(result, binding);
  }

及时简单只点子要就是是生成 通过findViewById方法去找到呼应之View
的代码。可以看出注解库最终还是调用的findViewById来探寻View的。

到此处ButterKnife注解库就分析了了。下面贴平段子最终生成的代码瞧瞧:

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view2131427417;

  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;

    View view;
    target.txt1 = Utils.findRequiredViewAsType(source, R.id.id1, "field 'txt1'", TextView.class);
    view = Utils.findRequiredView(source, R.id.btn, "method 'start'");
    view2131427417 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.start();
      }
    });
  }

  @Override
  @CallSuper
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;

    target.txt1 = null;

    view2131427417.setOnClickListener(null);
    view2131427417 = null;
  }
}

小结:首先通过注解来收获对应类中颇具设初始化的属性,通过processor编译生成对应的.java的类公事。这个仿佛公事中会于构造器中通过调用View的findViewById方法去初始化所有的习性(注意:到此地仅是非常成了.java类文件,并不曾同目标类绑定)。这个编译时转的类的构造器中需要传入对应之对象类作为参数。因此在目标类初始化时索要调用ButterKnife.bind(this)方法类进行绑定。在斯bind方法吃会由此反射得到编译时才转移的类的目标,这样就同目标类进行了绑定,也即是初始化目标类中之受诠释的习性。

相关文章