Butterknife源码

来源:互联网 发布:网络优化培训费多少 编辑:程序博客网 时间:2024/03/29 19:40

Butterknife用法

我相信学过Android开发应该基本上都用过Butterknife吧,就算没用过也听说过吧?毕竟是大名鼎鼎的Jake Wharton出品的东西,如果没用过,就分享下面这篇《Java基础之注解annotation》里面虽然是讲的Annotation,但是例子就是用注解加反射实现的低级的Butterknife。哈哈!用法里面大概也说了下。

前言

从 jdk5开始,Java增加了对元数据的支持,也就是Annotation,Annotation其实就是对代码的一种特殊标记,这些标记可以在编译,类加载和运行时被读取,并执行相应的处理。当然刚刚说了,Annotation只是一种标记,所以要是在代码里面不用这些标记也是能完成相应的工作的,只是有时候用注解能简化很多代码,看起来非常的简洁。

基本的Annotation

  • @Override——限定重写父类方法
  • @Deprecated——标示已过时
  • @SuppressWarning——抑制编译器警告
  • @SafeVarargs——这货与Java7里面的堆污染有关,具体想了解的,传送到这里

JDK的元Annotation

JDK除了提供上述的几种基本的Annotation外,还提供了几种Annotation,用于修饰其他的Annotation定义

  1. @Retention 这个是决定你Annotation存活的时间的,它包含一个RetationPolicy的value成员变量,用于指定它所修饰的Annotation保留时间,一般有:

    • Retationpolicy.CLASS:编译器将把Annotation记录在Class文件中,不过当java程序执行的时候,JVM将抛弃它。
    • Retationpolicy.SOURCE : Annotation只保留在原代码中,当编译器编译的时候就会抛弃它。
    • Retationpolicy.RUNTIME : 在Retationpolicy.CLASS的基础上,JVM执行的时候也不会抛弃它,所以我们一般在程序中可以通过反射来获得这个注解,然后进行处理。
  2. @Target 这个注解一般用来指定被修饰的Annotation修饰哪些元素,这个注解也包含一个value变量:

    • ElementType.ANNOTATION_TYPE : 指定该Annotation只能修饰Annotation。
    • ElementType.CONSTRUCTOR: 指定只能修饰构造器。
    • ElementType.FIELD: 指定只能成员变量。
    • ElementType.LOCAL_VARIABLE: 指定只能修饰局部变量。
    • ElementType.METHOD: 指定只能修饰方法。
    • ElementType.PACKAGE: 指定只能修饰包定义。
    • ElementType.PARAMETER: 指定只能修饰参数。
    • ElementType.TYPE: 指定可以修饰类,接口,枚举定义。
  3. @Document 这个注解修饰的Annotation类可以被javadoc工具提取成文档
  4. @Inherited 被他修饰的Annotation具有继承性

自定义Annotation

上面讲了一些jdk自带的Annotation,那么我们现在就可以用这些jdk自带的Annotation来实现一些我们想要的功能。由于最近在看butterknife的源码,那么我们就一步一步地模仿butterknife的实现吧。

首先先讲一下的用法吧:

@ContentView(R.layout.activity_main)public class MainActivity extends AppCompatActivity {    @ViewInject(R.id.text_view)    private TextView textView;    @OnClick(R.id.text_view)    private void onClick(View view){        textView.setText("我是click后的textview");    }    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ViewInjectUtils.inject(this);        textView.setText("我是click前的textview");    }}

上面是这篇文章最后的实现,自从用了注解后,妈妈再也不用担心我一遍一遍地写findViewById和setOnClickListener了。


编码

首先我们要先定义我们要用的接口,哦不!是注解。注意和接口不一样哦!这里我们先实现@ContentView的功能,再来实现@ViewInject@OnClick

package com.qhung.annotation.ioc.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by qhung on 2016/5/3. */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface ContentView {    int value();}

啊,这里的@Target和@Retention大家应该都清楚是什么意思了哈,定义注解的方式就是@interface 和接口的定义方式就少一个@哦,不要搞混了。里面有一个变量value,就是我们使用的时候@ContentView(R.layout.activity_main)指定的R.layout.activity_main布局文件,旨在自动注入布局文件。因为这里只有一个变量value,所以不用写成name=value的形式。

然后大家还记得我们在Activity里面调用的ViewInjectUtils.inject(this);?哈哈!其实我们处理注解的逻辑全在这个里面,那么我们就看看这个里面又是一番什么天地:

package com.qhung.annotation.ioc.annotation;import android.app.Activity;import android.view.View;import java.lang.reflect.Field;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * * Created by qhung on 2016/5/3. */public class ViewInjectUtils {    public static void inject(Activity activity) {        injectContentView(activity);    }    private static void injectContentView(Activity activity) {        Class<? extends Activity> clazz = activity.getClass();        ContentView contentView = clazz.getAnnotation(ContentView.class);        if (contentView != null) {            //如果这个activity上面存在这个注解的话,就取出这个注解对应的value值,其实就是前面说的布局文件。            int layoutId = contentView.value();            try {                Method setViewMethod = clazz.getMethod("setContentView", int.class);                setViewMethod.invoke(activity, layoutId);            } catch (Exception e) {                e.printStackTrace();            }        }    }}

原来ViewInjectUtils.inject(this)里面调用了injectContentView(activity),在injectContentView(activity)里面,我们拿到了Activity的Class,然后在第23行,我们拿到了这个class的ContentView注解,然后再通过反射调用setContentView方法,完成注入。其实这里是在运行的时候完成的,所以我们在定义注解的时候,设置为Retention为RUNTIME。

好了,这个功能到这里就完了。下面继续完成第二个功能:ViewInject
同样先贴上ViewInject类:

package com.qhung.annotation.ioc.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by qhung on 2016/5/3. */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface ViewInject {    int value();}

其实这个定义和上面ContentView的定义一样,然后我们再看看我们是怎么处理的:

    public static void inject(Activity activity) {        injectContentView(activity);        injectView(activity);    }    private static void injectView(Activity activity) {        Class<? extends Activity> clazz = activity.getClass();        //获得activity的所有成员变量        Field[] fields = clazz.getDeclaredFields();        for (Field field : fields) {        //获得每个成员变量上面的ViewInject注解,没有的话,就会返回null            ViewInject viewInject = field.getAnnotation(ViewInject.class);            if (viewInject != null) {                int viewId = viewInject.value();                View view = activity.findViewById(viewId);                try {                    field.setAccessible(true);                    field.set(activity, view);                } catch (IllegalAccessException e) {                    e.printStackTrace();                }            }        }    }}

获得所有属性,然后遍历带有ViewInject注解的属性,然后拿到ViewInject注解的View的id,然后通过activity.findViewById(viewId);获得这个View。然后设置给field。

最后一个功能:EventInject

这个功能稍微麻烦一点,因为我们平时设置的点击时间是用setiOnClickListener();然后View.OnClickListener是一个接口,不能用反射来获得他的实例,那么怎么办呢?

其实我们这里可以巧妙地用动态代理来完成,当view被点击的时候,我们通过动态代理来调用onclick就行。
下面是代码:

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface OnClick {    int[] value();}
public class ViewInjectUtils {    public static void inject(Activity activity) {        injectContentView(activity);        injectView(activity);        injectEvent(activity);    }    private static void injectEvent(final Activity activity) {        Class<? extends Activity> clazz = activity.getClass();        Method[] methods = clazz.getDeclaredMethods();        for (final Method method2 : methods) {            OnClick click = method2.getAnnotation(OnClick.class);            if (click != null) {                int[] viewId = click.value();                method2.setAccessible(true);                Object listener = Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(),                        new Class[]{View.OnClickListener.class}, new InvocationHandler() {                            @Override                            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                                return method2.invoke(activity, args);                            }                        });                try {                    for (int id : viewId) {                        View v = activity.findViewById(id);                        Method setClickListener = v.getClass().getMethod("setOnClickListener", View.OnClickListener.class);                        setClickListener.invoke(v, listener);                    }                } catch (Exception e) {                    e.printStackTrace();                }            }        }    }

listener是一个代理对象,然后我们调用setOnClickListener的时候,把这个代理对象传进去。当发生点击的时候,就会invoke方法,这时我们就可以调用带有onClick注解的method方法了。

下面看一下运行结果:


现在我们就完成了类似butterknife的功能了,不过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 Daxia");    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到底是怎样在编译的时候生成代码的?

作用到注解类型的运行时注解。

有了之前注解使用这篇文章的基础,我们知道对于编译时注解肯定是要通过自定义AbstractProcessor来解析的,所以接下来我们要去butterknife-compiler module中找一下对应的类。通过名字我们就能很简单的找到:

package butterknife.compiler;@AutoService(Processor.class)public final class ButterKnifeProcessor extends AbstractProcessor {   ...}

通过AutoService注解我们很容易看出来Butterknife也使用了Google Auto。当然它肯定也都用了javaopetandroid-apt,这里我们就不去分析了。

  • init(ProcessingEnvironment processingEnv) 被注解处理工具调用,参数ProcessingEnvironment 提供了Element,Filer,Messager等工具
  • getSupportedAnnotationTypes() 指定注解处理器是注册给那一个注解的,它是一个字符串的集合,意味着可以支持多个类型的注解,并且字符串是合法全名。
  • getSupportedSourceVersion 指定Java版本
  • process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 这个也是最主要的,在这里扫描和处理你的注解并生成Java代码,信息都在参数RoundEnvironment 里了,后面会介绍。
其他的一些方法我们就不继续看了,我们接下来看一下具体的核心处理方法,也就是ButterKnifeProcessor.process()方法:

@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {    // 查找、解析出所有的注解    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);    // 将注解后要生成的相关代码信息保存到BindingClass类中    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {      TypeElement typeElement = entry.getKey();      BindingClass bindingClass = entry.getValue();      // 输出生成的类      for (JavaFile javaFile : bindingClass.brewJava()) {        try {          javaFile.writeTo(filer);        } catch (IOException e) {          error(typeElement, "Unable to write view binder for type %s: %s", typeElement,              e.getMessage());        }      }    }    return true;  }

process()方法来看,我们需要主要分析两个部分:

  • findAndParseTargets():查找、解析所有的注解
  • bindingClass.brewJava():生成代码
第一步:findAndParseTargets()

先查看findAndParseTargets()方法的实现,里面解析的类型比较多,我们就以BindView为例进行说明:

private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {    Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();    scanForRClasses(env);    // Process each @BindArray element.    for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {      if (!SuperficialValidation.validateElement(element)) continue;      try {        parseResourceArray(element, targetClassMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindArray.class, e);      }    }    // Process each @BindBitmap element.    for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {      if (!SuperficialValidation.validateElement(element)) continue;      try {        parseResourceBitmap(element, targetClassMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindBitmap.class, e);      }    }    // Process each @BindBool element.    for (Element element : env.getElementsAnnotatedWith(BindBool.class)) {      if (!SuperficialValidation.validateElement(element)) continue;      try {        parseResourceBool(element, targetClassMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindBool.class, e);      }    }    // Process each @BindColor element.    for (Element element : env.getElementsAnnotatedWith(BindColor.class)) {      if (!SuperficialValidation.validateElement(element)) continue;      try {        parseResourceColor(element, targetClassMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindColor.class, e);      }    }    // Process each @BindDimen element.    for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) {      if (!SuperficialValidation.validateElement(element)) continue;      try {        parseResourceDimen(element, targetClassMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindDimen.class, e);      }    }    // Process each @BindDrawable element.    for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) {      if (!SuperficialValidation.validateElement(element)) continue;      try {        parseResourceDrawable(element, targetClassMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindDrawable.class, e);      }    }    // Process each @BindInt element.    for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {      if (!SuperficialValidation.validateElement(element)) continue;      try {        parseResourceInt(element, targetClassMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindInt.class, e);      }    }    // Process each @BindString element.    for (Element element : env.getElementsAnnotatedWith(BindString.class)) {      if (!SuperficialValidation.validateElement(element)) continue;      try {        parseResourceString(element, targetClassMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindString.class, e);      }    }    // 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);      }    }    // Process each @BindViews element.    for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {      if (!SuperficialValidation.validateElement(element)) continue;      try {        parseBindViews(element, targetClassMap, erasedTargetNames);      } catch (Exception e) {        logParsingError(element, BindViews.class, e);      }    }    // Process each annotation that corresponds to a listener.    for (Class<? extends Annotation> listener : LISTENERS) {      findAndParseListener(env, listener, targetClassMap, erasedTargetNames);    }    // Try to find a parent binder for each.    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {      TypeElement parentType = findParentType(entry.getKey(), erasedTargetNames);      if (parentType != null) {        BindingClass bindingClass = entry.getValue();        BindingClass parentBindingClass = targetClassMap.get(parentType);        bindingClass.setParent(parentBindingClass);      }    }    return targetClassMap;  }

继续看一下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();    }    // 必须是View类型或者接口    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;    }    // 通过注解的value拿到id    // Assemble information on the field.    int id = element.getAnnotation(BindView.class).value();    BindingClass bindingClass = targetClassMap.get(enclosingElement);    if (bindingClass != null) {      // 之前已经绑定过该id      ViewBindings viewBindings = bindingClass.getViewBinding(getId(id));      if (viewBindings != null && viewBindings.getFieldBinding() != null) {        FieldViewBinding existingBinding = viewBindings.getFieldBinding();        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 {      // 没有绑定过该id的话就去生成代码      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添加代码    bindingClass.addField(getId(id), binding);    // Add the type-erased version to the valid binding targets set.    erasedTargetNames.add(enclosingElement);  }

这里我们首先看到isInaccessibleViaGeneratedCode(),它里面判断了三个点:

  1. 验证方法修饰符不能为privatestatic
  2. 验证包含类型不能为非Class
  3. 验证包含类的可见性并不是private

接着我们来看isBindingInWrongPackage,它判断了这个类的包名,包名不能以android.和java.开头,butterknife不可以在Android Framework和JDK框架内部使用.

终于进入生成代码的阶段了,继续看一下getOrCreateTargetClass()的实现:

private BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap,      TypeElement enclosingElement) {    BindingClass bindingClass = targetClassMap.get(enclosingElement);    if (bindingClass == null) {      TypeName targetType = TypeName.get(enclosingElement.asType());      if (targetType instanceof ParameterizedTypeName) {        targetType = ((ParameterizedTypeName) targetType).rawType;      }      // 得到包名、类名      String packageName = getPackageName(enclosingElement);      String className = getClassName(enclosingElement, packageName);      // 用包名、类名和_ViewBinder等拼接成要生成的类的全名,这里会有两个类:$$_ViewBinder和$$_ViewBinding      ClassName binderClassName = ClassName.get(packageName, className + "_ViewBinder");      ClassName unbinderClassName = ClassName.get(packageName, className + "_ViewBinding");      boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);      // 将要生成的类名,$$_ViewBinder和$$_ViewBinding封装给BindingClass类      bindingClass = new BindingClass(targetType, binderClassName, unbinderClassName, isFinal);      targetClassMap.put(enclosingElement, bindingClass);    }    return bindingClass;  }

继续看一下BindingClass.addField():

void addField(Id id, FieldViewBinding binding) {    getOrCreateViewBindings(id).setFieldBinding(binding);  }

继续看getOrCreateViewBindings()以及setFieldBinding()方法:

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

然后看ViewBindings.setFieldBinding()方法:

public void setFieldBinding(FieldViewBinding fieldBinding) {    if (this.fieldBinding != null) {      throw new AssertionError();    }    this.fieldBinding = fieldBinding;  }

看到这里就把findAndParseTargets()方法分析完了。大体总结一下就是把一些变量、参数等初始化到了BindingClass类中。
也就是说上面process()方法中的第一步已经分析完了,下面我们来继续看第二部分.

第二步:bindingClass.brewJava()

继续查看BindingClass.brewJava()方法的实现:

Collection<JavaFile> brewJava() {    TypeSpec.Builder result = TypeSpec.classBuilder(binderClassName)        .addModifiers(PUBLIC, FINAL)        .addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, targetTypeName));    result.addMethod(createBindMethod(targetTypeName));    List<JavaFile> files = new ArrayList<>();    if (isGeneratingUnbinder()) {      // 生成$$_ViewBinding类      files.add(JavaFile.builder(unbinderClassName.packageName(), createUnbinderClass())          .addFileComment("Generated code from Butter Knife. Do not modify!")          .build()      );    } else if (!isFinal) {      result.addMethod(createBindToTargetMethod());    }    // 生成$$_ViewBinder类    files.add(JavaFile.builder(binderClassName.packageName(), result.build())        .addFileComment("Generated code from Butter Knife. Do not modify!")        .build());    return files;  }

看到这里感觉不用再继续分析了,该方法就是使用javaopet来生成对应$$_ViewBinder.java类。

总结一下:

  • ButterKnifeProcessor会生成$$_ViewBinder类并实现了ViewBinder接口。
  • $$_ViewBinder类中包含了所有对应的代码,会通过注解去解析到id等,然后通过findViewById()等方法找到对应的控件,并且复制给调用该方法的来中的变量。这样就等同于我们直接
    使用View v = findViewByid(R.id.xx)来进行初始化控件。
  • 上面虽然生成了$$_ViewBinder类,但是如何去调用呢? 就是在调用ButterKnife.bind(this)时执行,该方法会通过反射去实例化对应的$$_ViewBinder类,并且调用该类的bind()方法。

  • Butterknife除了在Butterknife.bind()方法中使用反射之外,其他注解的处理都是通过编译时注解使用,所以不会影响效率。

  • 使用Butterknife是不要将变量声明为private类型,因为$$_ViewBinder类中会去直接调用变量赋值,如果声明为private将无法赋值。
    java
    @BindView(R2.id.title) TextView title;
0 0
原创粉丝点击