ButterKnife解析

本文主要介绍Android之神JakeWharton的一个注解框架,听说现在面试官现在面试都会问知不知道JakeWharton,不知道的直接略过。废话不多说,我们接下来就简单介绍下ButterKnife的实现,希望对大家有所帮助,还有一点,以下故事纯属虚构,如有雷同,纯属巧合。

目录

  • ButterKnife的起源和工作流程
  • ButterKnife模块
  • ButterKnife处理器模块
  • 总结

ButterKnife的起源和工作流程

起源

1.我们平常一般都是在xml文件中写好布局,然后在Activity中通过下面代码查找和处理控件

public class MainActivity extends Activity implements View.OnClickListener {
    private TextView mTextview;
    private Button mButton;
    private ImageView mImageview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        mTextview = (TextView) findViewById(R.id.textview);
        mButton = (Button) findViewById(R.id.button);
        mImageview = (ImageView) findViewById(R.id.imageview);
        mButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                Toast.makeText(this, "罗马不是一天建成的", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

2.显然这些findViewById和setOnClickListener很是碍眼,于是有人想是不是可以把findViewById和setOnClickListener这些操作放外边儿呢,这样在MainActivity里边只需要声明变量和回调方法,我们就可以只关注我们需要关注的,于是乎就有了如下代码

public class MainActivity extends Activity {
    TextView mTextview;
    Button mButton;
    ImageView mImageview;
    private TestMainActivity_ViewBinding mBind;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBind = TestButterKnife.bind(this);
    }

    public void onClick() {
        Toast.makeText(this, "罗马不是一天建成的", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBind.unbind();
    }
}
public class TestButterKnife {
    public static TestMainActivity_ViewBinding bind(Activity target) {
        return new TestMainActivity_ViewBinding((MainActivity) target);
    }
}
public class TestMainActivity_ViewBinding {
    private MainActivity target;

    public TestMainActivity_ViewBinding(MainActivity target) {
        this(target, target.getWindow().getDecorView());
    }

    public TestMainActivity_ViewBinding(final MainActivity target, View source) {
        this.target = target;
        target.mTextview = (TextView) source.findViewById(R.id.textview);
        target.mImageview = (ImageView) source.findViewById(R.id.imageview);
        target.mButton = (Button) source.findViewById(R.id.button);
        target.mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                target.onClick();
            }
        });
    }

    public void unbind() {
        MainActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;
        target.mTextview = null;
        target.mImageview = null;
        target.mButton.setOnClickListener(null);
        target.mButton = null;
    }
}

3.看到上面的代码一定有很多人都想吐,我也想说me too,这样岂不是要码很多代码,还要建好多类,有点得不偿失的赶脚。这时候Android之神JakeWharton站出来了,发表了一席讲话,大体内容是说可以写个小框架,让用户在MainActivity里边只需要写点注解,其它的事情交给框架来做,也不需要建那么类,码很多代码,于是就有了如下设计

public class MainActivity extends Activity {
    @BindView(R.id.textview)
    TextView mTextview;
    @BindView(R.id.button)
    Button mButton;
    @BindView(R.id.imageview)
    ImageView mImageview;
    private Unbinder mBind;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBind = ButterKnife.bind(this);
    }

    @OnClick(R.id.button)
    public void onClick() {
        Toast.makeText(this, "罗马不是一天建成的", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBind.unbind();
    }
}

工作流程

看完上面的设计,肯定有人就会想,这样的话需要使用运行时注解实现吧,在ButterKnife的bind(this)方法执行的时候通过反射获取MainActivity中所有的带有注解的属性并且获得注解中的R.id.xxx值,最后还是通过反射调用Activity.findViewById()方法来获取View,并赋值给Activity中的某个属性。可是这样做不会有问题吗,我们都知道在Activity运行时大量使用反射会影响App的运行性能,造成卡顿等一系列问题。其实这种问题JakeWharton早就想到了,于是乎选用了编译时注解来实现,也就是Java Annotation Processing技术。下面开始简单介绍下ButterKnife的工作流程,该库主要使用编译时注解和javapoet在编译时生成辅助类,在运行时通过反射调用辅助类的构造和方法来实现。

大体流程.png

1.编译时ButterKnifeProcessor类的process()方法处理过程如下

  • 获取所有的标记了@BindView、@OnClick等注解的元素
  • 定义一个key为类级别的元素,value为注解信息的Map集合,根据获取的元素可以拿到当前类级别的元素和注解标记的信息,然后put进去,这样Map里边就保存了所有要生成类和类中的信息
  • 根据Map集合通过 javapoet 生成类似MainActivity_ViewBinding这样的很多类,包括包名,导包,类名,字段,构造,方法等信息都会生成,可见这个库的强大,这种库最难的应该就是导包了吧

2.运行时

  • ButterKnife.bind(this);这个方法会反射创建并返回MainActivity_ViewBinding这样的实例,MainActivity_ViewBinding的构造就会执行,构造中会进行findViewById和setOnClickListener这些操作,这与起源中第2个模块里边的TestMainActivity_ViewBinding代码很相似
  • 当然通过ButterKnife.bind(this);的返回值,你可以在onDestroy()中调用MainActivity_ViewBinding的unbind()方法来解除绑定释放资源

ButterKnife模块

这个模块主要有三个东东,一个是ButterKnife类,一个Utils类,一个是Unbinder接口。ButterKnife主要是用于运行时反射调用辅助类和返回辅助类实例,Utils类会被生成辅助类调用,Unbinder是生成的辅助类需要实现的接口,下面依次来介绍

ButterKnife类

1.mBind = ButterKnife.bind(this),会走如下代码

  //UiThread表明这个方法必须在UI线程调用
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }

2.接着调用createBinding(target, sourceView)

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    //主要调用如下方法,该方法会返回辅助类的构造器
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    if (constructor == null) {
      return Unbinder.EMPTY;
    }
    //省去了try-catch,这里创建辅助类的实例并返回,注意这里会调用辅助类两个参数的构造方法
    return constructor.newInstance(target, source);
}

3.下面我们看findBindingConstructorForClass(targetClass),这里采用了享元模式的设计,这种设计在很对框架中都会有,这里维护的是一个Map集合,集合中有这个key对应的value直接返回,没有才会构建,构建完后会放入Map中,这样大大减少了内存中对象的数量

  //BINDINGS是一个key为Class,value为构造器的Map
  static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
     //集合中通过key来获取构造器
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      //构造器不为空,说明之前创建过就会直接返回
      return bindingCtor;
    }
    String clsName = cls.getName();
    //类名不能以android.和java.开头
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      //可以看到生成的辅助类的类名是原始类名加上_ViewBinding,比如MainActivity_ViewBinding
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //创建构造器
      bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    //创建完后放入Map集合中
    BINDINGS.put(cls, bindingCtor);
    //返回构造器
    return bindingCtor;
  }
Utils类

这里简单介绍几个方法,后面生成的辅助类会调用这个类中方法
1.findRequiredViewAsType()

  public static <T> T findRequiredViewAsType(View source, @IdRes int id,String who,Class<T> cls) {
    //这里会调用下面2中的方法拿到一个没有强转的View
    View view = findRequiredView(source, id, who);
    //会调用3中的方法根据返回一个强制后的T,T表示@BindView所修饰控件类型,比如TextView、Button等
    return castView(view, id, who, cls);
  }

2.findRequiredView(View source, @IdRes int id, String who)

  public static View findRequiredView(View source, @IdRes int id, String who) {
    //我们期望看到的findViewById在这里
    View view = source.findViewById(id);
    if (view != null) {
      return view;
    }
    //省略异常处理
  }

3.castView(View view, @IdRes int id, String who, Class<T> cls)

  public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
      //省略try-catch,这里调用Class类的cast方法进行强转,我们稍微看下这个方法
      return cls.cast(view);
  }
    //Class类中的cast方法
    public T cast(Object obj) {
        if (obj != null && !isInstance(obj))
            throw new ClassCastException(cannotCastMsg(obj));
        //这里强转了下然后返回
        return (T) obj;
    }
Unbinder接口

这个接口中就一个方法和一个空实现的成员变量

public interface Unbinder {
  @UiThread void unbind();
  //这个成员变量会在构造器为null的异常情况下直接返回,可以在上面ButterKnife类中的createBinding方法中看到
  Unbinder EMPTY = new Unbinder() {
    @Override public void unbind() {}
  };
}

ButterKnife处理器模块

这个模块中我们只关注ButterKnifeProcessor这个类,该类继承了AbstractProcessor抽象类,下面我们看它重写的几个方法

1.首先我们看@AutoService注解,这是一个其他注解处理器中引入的注解。它是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor文件的,我们直接用即可

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
      //省略
}

2.接着我们看init(ProcessingEnvironment env),注解处理器工具APT会调用这个方法并传入ProcessingEnvironment ,我理解的是该方法是提供给我们用来访问APT的环境

  private int sdk = 1;
  private static final String OPTION_SDK_INT = "butterknife.minSdk";
  private static final String OPTION_DEBUGGABLE = "butterknife.debuggable";
  private boolean debuggable = true;
  private Elements elementUtils;
  private Types typeUtils;
  private Filer filer;
  private Trees trees;
  @Override
  public synchronized void init(ProcessingEnvironment env) {
    super.init(env);
    //env.getOptions()拿到的是传递给注释处理工具的Map选项
    String sdk = env.getOptions().get(OPTION_SDK_INT);
    if (sdk != null) {
        //省去try-catch
        this.sdk = Integer.parseInt(sdk);
    }
    debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
    //获取元素实用工具
    elementUtils = env.getElementUtils();
    //获取类型实用工具
    typeUtils = env.getTypeUtils();
    //获取用来创建文件的Filter
    filer = env.getFiler();
    try {
      trees = Trees.instance(processingEnv);
    } catch (IllegalArgumentException ignored) {
    }
  }

3.我们看getSupportedSourceVersion(),这个方法用来指定你使用的Java版本

  @Override 
  public SourceVersion getSupportedSourceVersion() {
    //通常这样返回就行了,它会根据你的环境返回对应的版本枚举
    return SourceVersion.latestSupported();
  }

4.接下来就是getSupportedAnnotationTypes()方法,指定支持的注解

  @Override 
  public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
      //annotation.getCanonicalName()为获取全类名,即包名+类名
      types.add(annotation.getCanonicalName());
    }
    return types;
  }
  private Set<Class<? extends Annotation>> getSupportedAnnotations() {
    Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
    annotations.add(BindAnim.class);
    annotations.add(BindArray.class);
    annotations.add(BindBitmap.class);
    annotations.add(BindBool.class);
    annotations.add(BindColor.class);
    annotations.add(BindDimen.class);
    annotations.add(BindDrawable.class);
    annotations.add(BindFloat.class);
    annotations.add(BindFont.class);
    annotations.add(BindInt.class);
    annotations.add(BindString.class);
    annotations.add(BindView.class);
    annotations.add(BindViews.class);
    annotations.addAll(LISTENERS);
    return annotations;
  }

5.最后来详细介绍下process(),这个方法相当于每个处理器的main()函数,在这里可以写你的扫描、处理注解以及生成Java文件的代码,因此这个方法是整个处理器模块的关键

  @Override 
  public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //根据env获取一个Map集合,其中key为类级别的元素,value为BindingSet
    //BindingSet是一个自定义类,其中含有当前类中所有的注解信息
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();
      //注意下面的两行代码就是生成辅助类啦,由于需要用到JavaPoet库,后面会详细讲解
      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
    return false;
  }
首先我们来看findAndParseTargets(env)是如何构建Map集合的
  private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    //key为类级别的元素,value为BindingSet的内部类Builder,看到Builder我们应该都很敏感,这是典型的
    //建造者模式,这种模式特别常见,不管是在第三方库OkHttp中,还是在Android提供的对话框API中都可以看见
    //它的身影,对于设计模式我们这里不再详细展开
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    //Set集合中保存了所有类级别的元素
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    scanForRClasses(env);
    //由于定义的注解很多,这里省略了其他注解,只关注@BindView
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      try {
        //解析@BindView注解到buildMap中
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
    //builderMap是key为类级别的元素,value为BindingSet的内部类Builder,这里会转为bindingMap,即
    //把value通过builder()方法构建成BindingSet,这就是典型的建造者模式
    Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
        new ArrayDeque<>(builderMap.entrySet());
    Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
    while (!entries.isEmpty()) {
      Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
      TypeElement type = entry.getKey();
      BindingSet.Builder builder = entry.getValue();
      //注意这是在while循环里边,这个方法中会根据当前类级别的元素查找父元素,如果没有就返回null,如果
      //有父元素就返回父元素,从这里可以看出来我们的注解是支持继承的
      TypeElement parentType = findParentType(type, erasedTargetNames);
      if (parentType == null) {
        //父元素为null,直接put当前元素
        bindingMap.put(type, builder.build());
      } else {
        //从bindingMap中先获取父元素的BindingSet
        BindingSet parentBinding = bindingMap.get(parentType);
        if (parentBinding != null) {
          //父元素的BindingSet不为null,就需要为当前元素设置它的父元素
          builder.setParent(parentBinding);
          //然后put到bindingMap中
          bindingMap.put(type, builder.build());
        } else {
          //父元素的BindingSet是null,就先把当前的Entry放到队尾,继续下一次循环
          entries.addLast(entry);
        }
      }
    }
    //返回key为类级别的元素,value为BindingSet的集合
    return bindingMap;
  }
  • 接下来我们看上面方法中的parseBindView()和findParentType(),我们先看findParentType()是如何根据当前类级别的元素查找父元素
  private TypeElement findParentType(TypeElement typeElement, Set<TypeElement> parents) {
    TypeMirror type;
    while (true) {
      //根据当前类级别的元素查找父元素
      type = typeElement.getSuperclass();
      if (type.getKind() == TypeKind.NONE) {
        //父元素为null,直接返回null
        return null;
      }
      //parents集合中保存的是包含有我们自定义注解的类级别的元素,找到
      //父元素并且父元素是个有效的即在集合里,就返回父元素
      typeElement = (TypeElement) ((DeclaredType) type).asElement();
      if (parents.contains(typeElement)) {
        return typeElement;
      }
    }
  }
  • 再先看parseBindView()是如何解析@BindView注解到buildMap中的
  private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    //获取当前元素的类级别的元素
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    //生成的代码是不是可以访问,由于我们是通过target.mTextview来访问的,所以这个方法会校验@BindView修饰的
    //字段是不是private、static的,@BindView是不是在类里边,如果在类里边那么类是不是private的
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element);
    //下面几行代码主要是校验@BindView修饰的字段类型,必须是一个View或者是一个接口,否则就毫无意义
    //获取@BindView修饰的字段类型,比如TextView类型
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    //校验修饰的类型是不是View的子类或接口,否则结束
    if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
      if (elementType.getKind() == TypeKind.ERROR) {
        note(element, "@%s field with unresolved type (%s) "
                + "must elsewhere be generated as a View or interface. (%s.%s)",
            BindView.class.getSimpleName(), elementType, qualifiedName, simpleName);
      } else {
        error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
            BindView.class.getSimpleName(), qualifiedName, simpleName);
        hasError = true;
      }
    }
    if (hasError) {
      return;
    }
    //获取@BindView的值,即R.id.xxx
    int id = element.getAnnotation(BindView.class).value();
    //享元模式的设计,根据当前元素的类级别的元素先从Map中获取BindingSet的内部类Builder
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    //把int类型的id包装成了QualifiedId类
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
    if (builder != null) {
       //getId(qualifiedId)把QualifiedId类中的id又封装成了Id类,校验Id类是否已经存在
      String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
      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为空,说明当前类还没有对应的value,需要new一个出来,并放到builderMap中
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }
    //@BindView修饰的字段的简单名称,即变量名mTextView
    String name = simpleName.toString();
    //@BindView修饰的字段的类型,比如TextView
    TypeName type = TypeName.get(elementType);
    //是否必须是字段
    boolean required = isFieldRequired(element);
    //调用BindingSet的内部类Builder中的addField方法封装解析的信息
    builder.addField(getId(qualifiedId), new FieldViewBinding(name, type, required));
    //给所有含有自定义注解的类组成的Set集合中添加元素
    erasedTargetNames.add(enclosingElement);
  }
  private BindingSet.Builder getOrCreateBindingBuilder(
      Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder == null) {
      //builder为null,就构建一个BindingSet.Builder,放入集合中,并返回
      builder = BindingSet.newBuilder(enclosingElement);
      builderMap.put(enclosingElement, builder);
    }
    return builder;
  }
  static Builder newBuilder(TypeElement enclosingElement) {
    TypeMirror typeMirror = enclosingElement.asType();
    //类级别的元素是不是View的子类
    boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
    //类级别的元素是不是Activity的子类
    boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
    //类级别的元素是不是Dialog的子类
    boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);
    TypeName targetType = TypeName.get(typeMirror);
    if (targetType instanceof ParameterizedTypeName) {
      targetType = ((ParameterizedTypeName) targetType).rawType;
    }
    //根据当前类级别的元素获取包名
    String packageName = getPackage(enclosingElement).getQualifiedName().toString();
     //substring截取后一般是MainActivity这样的字符串,然后replace的话会没有效果,如果有.则用$代替
    String className = enclosingElement.getQualifiedName().toString().substring(
        packageName.length() + 1).replace('.', '$');
    //这里可以看到要生成的包名,要生成的类名,格式如MainActivity_ViewBinding
    ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");
    //当前类是不是被final修饰
    boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    //直接通过构造new一个Builder,第一个参数表示是不是一个class,第二个参数中含有要生成的类名和包名,其它都是布尔类型的参数
    return new Builder(targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
  }

总结下,在parseBindView()方法中解析出来的信息都放在BindingSet的内部类Builder中,包含的信息有包名、原始类名、要生成的类名、R.id.xxx、是不是字段、变量名mTextView、变量类型TextView等信息。而BindingSet的内部类Builder又会通过调用build()方法构建一个BindSet实例并把这些信息传递过去,这就是典型的建造者模式,在BindSet中有了这些信息,下面我们就可以生成辅助类啦。

最后我们看binding.brewJava(sdk, debuggable)是如何生成辅助类的

1.回到最初的process()方法,我们讲完了如何构建Map集合,接着我们讲解如何生成辅助类

  @Override 
  public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //根据env获取一个Map集合,其中key为类级别的元素,value为BindingSet
    //BindingSet是一个自定义类,其中含有当前类中所有的注解信息
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();
      //注意下面的两行代码就是生成辅助类啦,由于需要用到JavaPoet库,后面会详细讲解
      JavaFile javaFile = binding.brewJava(sdk, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }
    return false;
  }

2.需要用到javapoet,因此打开这个库我们稍微看下它是如何使用的

  • 比如我们想要生成一个普通的java类,其中有main方法,方法中有syso
  package com.example.helloworld;
  public final class HelloWorld {
    public static void main(String[] args) {
      System.out.println("Hello, JavaPoet!");
    }
  }
  • 那我们就可以调用下面的方法来实现,可以看到又是建造者builder模式,真是无处不见啊,呼哈
  //方法级别的,定义方法名
  MethodSpec main = MethodSpec.methodBuilder("main")
       //定义修饰符
      .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
       //定义返回值
      .returns(void.class)
       //定义方法参数
      .addParameter(String[].class, "args")
       //定义方法中的syso
      .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
      .build();

  //类级别的,定义类名
  TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
      //添加修饰符
      .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
      //把上面定义的main添加到类中
      .addMethod(main)
      .build();

   //JavaFile中保存了所有的信息,包名和上面定义的类信息,方法信息
  JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
      .build();

  javaFile.writeTo(System.out);

3.简单看了javapoet的使用,继续看我们的代码,而我们知道生成辅助类的所有信息都在JavaFile中,我们看JavaFile是如何生成的,也就是上面的brewJava()

  JavaFile javaFile = binding.brewJava(sdk, debuggable);
  JavaFile brewJava(int sdk, boolean debuggable) {
    //bindingClassName是ClassName类型,它是对包名和类名的封装,这里可以看到获取包名,类和类里边的信息是
    //通过createType()方法获取的,也就是TypeSpec
    return JavaFile.builder(bindingClassName.packageName(), createType(sdk, debuggable))
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }
  private TypeSpec createType(int sdk, boolean debuggable) {
    //类级别的,定义类名,bindingClassName是ClassName类型,它是对包名和类名的封装
    //上面获取的是包名,这里获取的是辅助类的类名
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC);
    if (isFinal) {
      result.addModifiers(FINAL);
    }
    //判断有没有父类,即生成的辅助类是不是也要从父类继承
    if (parentBinding != null) {
      //有父类,那么生成的辅助类也要从父类继承
      result.superclass(parentBinding.bindingClassName);
    } else {
      //没有父类,就让辅助类实现Unbinder
      result.addSuperinterface(UNBINDER);
    }
    //设置类似这样的字段private MainActivity target;
    if (hasTargetField()) {
      result.addField(targetTypeName, "target", PRIVATE);
    }
    //判断是不是View、Activity、Dialog的子类,来生成不同构造
    if (isView) {
      result.addMethod(createBindingConstructorForView());
    } else if (isActivity) {
      //这里我们只关注这个构造的生成,这个方法用来生成一个参数MainActivity target的构造
      result.addMethod(createBindingConstructorForActivity());
    } else if (isDialog) {
      result.addMethod(createBindingConstructorForDialog());
    }
    if (!constructorNeedsView()) {
      result.addMethod(createBindingViewDelegateConstructor());
    }
    //前面是只有一个参数MainActivity target构造
    //这里是两个参数final MainActivity target, View source的构造
    result.addMethod(createBindingConstructor(sdk, debuggable));
    if (hasViewBindings() || parentBinding == null) {
      //没有父类,那么我们前面实现了Unbinder接口,需要实现它里面的unbind方法
      result.addMethod(createBindingUnbindMethod(result));
    }
    return result.build();
  }

4.上面方法中调用的三个方法我们还没看,其中有一个参数的构造、两个参数的构造、unbind()方法,接下来我们挨着看

  • createBindingConstructorForActivity()生成一个参数MainActivity target的构造
  private MethodSpec createBindingConstructorForActivity() {
    MethodSpec.Builder builder = MethodSpec.constructorBuilder()
         //添加@UiThread注解
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC)
        //添加构造中的参数,如MainActivity target
        .addParameter(targetTypeName, "target");
    if (constructorNeedsView()) {
      builder.addStatement("this(target, target.getWindow().getDecorView())");
    } else {
      //这里是一个参数的构造不需要View,构造中添加this(target, target)
      builder.addStatement("this(target, target)");
    }
    return builder.build();
  }
  • createBindingConstructor(sdk, debuggable)生成两个参数final MainActivity target, View source的构造
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
         //添加@UiThread注解
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC);
    //判断是否有方法绑定,即是否有onClick()这样的回调方法
    if (hasMethodBindings()) {
      //由于是在匿名内部类方法里边调用构造函数的第一个参数,需要加上final修饰
      constructor.addParameter(targetTypeName, "target", FINAL);
    } else {
      //没有方法绑定,只需要添加构造函数中的第一个参数MainActivity target
      constructor.addParameter(targetTypeName, "target");
    }
    
    if (constructorNeedsView()) {
      //构造函数的第二个参数就是View source
      constructor.addParameter(VIEW, "source");
    } else {
      constructor.addParameter(CONTEXT, "context");
    }
    //有一些警告的话添加java提供的@SuppressWarnings注解
    if (hasUnqualifiedResourceBindings()) {
      constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
          .addMember("value", "$S", "ResourceType")
          .build());
    }
    if (hasOnTouchMethodBindings()) {
      constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
          .addMember("value", "$S", "ClickableViewAccessibility")
          .build());
    }
    //有父类的情况下,需要根据情况添加如下代码
    if (parentBinding != null) {
      if (parentBinding.constructorNeedsView()) {
        constructor.addStatement("super(target, source)");
      } else if (constructorNeedsView()) {
        constructor.addStatement("super(target, source.getContext())");
      } else {
        constructor.addStatement("super(target, context)");
      }
      constructor.addCode("\n");
    }
    if (hasTargetField()) {
      //会执行这里,添加this.target = target,并且换行
      constructor.addStatement("this.target = target");
      constructor.addCode("\n");
    }
    //判断是不是有View绑定,肯定有@BindView
    if (hasViewBindings()) {
      if (hasViewLocal()) {
        //在构造函数中定义一个局部变量View view,用来view.setOnClickListener()
        constructor.addStatement("$T view", VIEW);
      }
      for (ViewBinding binding : viewBindings) {
        //这是在for循环里面,这个方法会间接调用findViewById和强转的操作,接下来主要看这个方法
        addViewBinding(constructor, binding, debuggable);
      }
      for (FieldCollectionViewBinding binding : collectionBindings) {
        constructor.addStatement("$L", binding.render(debuggable));
      }
      //资源绑定不为空,才会换行
      if (!resourceBindings.isEmpty()) {
        constructor.addCode("\n");
      }
    }
    //资源的绑定不为空才会执行
    if (!resourceBindings.isEmpty()) {
      if (constructorNeedsView()) {
        constructor.addStatement("$T context = source.getContext()", CONTEXT);
      }
      if (hasResourceBindingsNeedingResource(sdk)) {
        constructor.addStatement("$T res = context.getResources()", RESOURCES);
      }
      for (ResourceBinding binding : resourceBindings) {
        constructor.addStatement("$L", binding.render(sdk));
      }
    }
    return constructor.build();
  }
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
    if (binding.isSingleFieldBinding()) {
      FieldViewBinding fieldBinding = binding.getFieldBinding();
      //先写target.mTextview = ,这里可以看到是直接通过.调用的,因此我们写的字段不能是private的
      CodeBlock.Builder builder = CodeBlock.builder()
          .add("target.$L = ", fieldBinding.getName());
      boolean requiresCast = requiresCast(fieldBinding.getType());
      if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
        builder.add("source.findViewById($L)", binding.getId().code);
      } else {
        //下面的代码会组合成一行代码,Utils中的findRequiredViewAsType()会进行find和强转操作,上面介绍过了
        //Utils.findRequiredViewAsType(source, R.id.textview, "field 'mTextview'", TextView.class);
        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;
    }
  • createBindingUnbindMethod(result)生成unbind()方法,即实现Unbinder接口中的unbind()方法
private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass) {
    //定义unbind()方法,并且添加@Override注解
    MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")
        .addAnnotation(Override.class)
        .addModifiers(PUBLIC);
    if (!isFinal && parentBinding == null) {
      //@CallSuper是android提供的注解,表示调用父类
      result.addAnnotation(CALL_SUPER);
    }
    if (hasTargetField()) {
      if (hasFieldBindings()) {
        //添加MainActivity target = this.target
        result.addStatement("$T target = this.target", targetTypeName);
      }
      //添加if (target == null) throw new IllegalStateException("Bindings already cleared.")
      result.addStatement("if (target == null) throw new $T($S)", IllegalStateException.class,
          "Bindings already cleared.");
      //添加this.target = null
      result.addStatement("$N = null", hasFieldBindings() ? "this.target" : "target");
      //换行
      result.addCode("\n");
      for (ViewBinding binding : viewBindings) {
        if (binding.getFieldBinding() != null) {
          //循环添加类似这样的代码来解除绑定,target.mTextview = null;
          result.addStatement("target.$L = null", binding.getFieldBinding().getName());
        }
      }
      for (FieldCollectionViewBinding binding : collectionBindings) {
        result.addStatement("target.$L = null", binding.name);
      }
    }
     //有绑定方法,如设置监听事件回调方法才会走这个if
    if (hasMethodBindings()) {
      result.addCode("\n");
      for (ViewBinding binding : viewBindings) {
        addFieldAndUnbindStatement(bindingClass, result, binding);
      }
    }
    if (parentBinding != null) {
      //有父类才会换行,并且添加super.unbind()
      result.addCode("\n");
      result.addStatement("super.unbind()");
    }
    return result.build();
  }

总结下,至此生成辅助类的代码就简单介绍完了,我们只是简单介绍了下@BindView,其它的注解都没介绍,有兴趣的读者可以自己去扒Butterknife源码,我们可以在\app\build\generated\source\apt\debug\目录下找到编译时生成的辅助类,下面我们列出一个简单的

package com.zeit.butter;

import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;

public class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  private View view2131427415;

  @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.mTextview = Utils.findRequiredViewAsType(source, R.id.textview, "field 'mTextview'", TextView.class);
    view = Utils.findRequiredView(source, R.id.button, "field 'mButton' and method 'onClick'");
    target.mButton = Utils.castView(view, R.id.button, "field 'mButton'", Button.class);
    view2131427415 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.onClick();
      }
    });
    target.mImageview = Utils.findRequiredViewAsType(source, R.id.imageview, "field 'mImageview'", ImageView.class);
  }

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

    target.mTextview = null;
    target.mButton = null;
    target.mImageview = null;

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

总结

JakeWharton的Butterknife,俗称"黄油刀",可以帮助我们提高开发效率,尤其是配合AS插件的使用,下面推荐一个插件Android-Butterknife-Zelezny。而阅读源码可以让我们知其然知其所以然,也可以学到很多设计模式。建议阅读第三方库或Android提供的API等源码之前,先从设计模式学起,推荐《Android源码设计模式解析与实战》。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容