Android框架源码解析之(三)ButterKnife

注:所有分析基于butterknife:8.4.0

源码目录:https://github.com/JakeWharton/butterknife
其中最主要的3个模块是:

  1. Butterknife注解处理器https://github.com/JakeWharton/butterknife/tree/master/butterknife-compiler
  2. Butterknife各种注解https://github.com/JakeWharton/butterknife/tree/master/butterknife-annotations
  3. Butterknife主工程https://github.com/JakeWharton/butterknife/tree/master/butterknife

一、首先看一下使用

在MainActivity中绑定TextView


public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv)
    TextView tv;

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

    }
}

Rebuild一下在build/generared/source/apt中生成辅助文件,看一下生成的辅助类


import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;

public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
  protected T target;

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

    target.tv = Utils.findRequiredViewAsType(source, R.id.tv, "field 'tv'", TextView.class);
  }

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

    target.tv = null;

    this.target = null;
  }
}

二、源码分析
首先大概说一下流程吧。
Butterknife并没有使用反射机制,而是使用编译时注解动态绑定。
其中最要的是这注解处理器ButterKnifeProcessor

  1. ButterKnifeProcessor扫描java代码中的所有ButterKnife注解
  2. ButterKnifeProcessor 根据扫描结果,给对应的activity生成ClassName$$_ViewBinder
  3. 调用Bind方法加载 ViewBinder类,实现动态绑定

源码:
先来看一下@Bindview 注解

@Retention(CLASS) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}
//@Retention(CLASS)表明是编译时注解
//@Target(FIELD)表明应用于成员变量

使用@BindView 绑定TextView控件,后文会用到这些代码



public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv)
    TextView tv;

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

    }
}

1、ButterKnifeProcessor源码分析
它的主要处理逻辑都在process中,如下所示

public final class ButterKnifeProcessor extends AbstractProcessor {

....
  @Override public boolean process(Set elements, RoundEnvironment env) {

                                              //1、查找和解析注解,将结果放在bindingMap中
    Map bindingMap = findAndParseTargets(env);

    for (Map.Entry entry : bindingMap.entrySet()) {

      //2、遍历所有注解

      TypeElement typeElement = entry.getKey();
      BindingSet binding = entry.getValue();

      //3、根据注解生成java文件
      JavaFile javaFile = binding.brewJava(sdk, debuggable, useAndroidX);
      try {

        //4、写入java文件

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

    return false;
  }


}

主要流程是:

扫描所有Butterknife注解
生成辅助类
将辅助类写到文件夹中

接下来查看findAndParseTargets()方法

 private Map findAndParseTargets(RoundEnvironment env) {
    Map builderMap = new LinkedHashMap<>();
    Set erasedTargetNames = new LinkedHashSet<>();



....


    // Process each @BindView element.
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
      //重点在这块
        parseBindView(element, builderMap, erasedTargetNames);
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }

findAndParseTargets方法会查找所有的butterknife注解来进行解析,前面我们使用了@BindView注解,因此这里只截取@Bindview 注解的部分。

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

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

    //方法修饰符不能为private和static    包含类型不能为非class  包含类型不能为private

    // Verify that the target type extends from View.
    //验证target继承自view
    TypeMirror elementType = element.asType();
    if (elementType.getKind() == TypeKind.TYPEVAR) {
      TypeVariable typeVariable = (TypeVariable) elementType;
      elementType = typeVariable.getUpperBound();
    }
    Name qualifiedName = enclosingElement.getQualifiedName();
    Name simpleName = element.getSimpleName();
    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;
    }

    // Assemble information on the field.
    //获取注解标注的值
    int id = element.getAnnotation(BindView.class).value();


    BindingSet.Builder builder = builderMap.get(enclosingElement);
    Id resourceId = elementToId(element, BindView.class, id);

    //判断 BindingSet.Builder 是否存在,若存在则复用,不存在,则创建
    if (builder != null) {
      String existingBindingName = builder.findExistingBindingName(resourceId);
      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 = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);

    builder.addField(resourceId, new FieldViewBinding(name, type, required));
    //将注解修饰的类型信息存储到FieldViewBinding中,注解所修饰的类型信息存储在BindingSet中
    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
  }

接下来查看如何根据注解生成java文件:

  JavaFile brewJava(int sdk, boolean debuggable, boolean useAndroidX) {
    TypeSpec bindingConfiguration = createType(sdk, debuggable, useAndroidX);
    return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
  }
  //将使用注解的类生成一个javafile

接下来将javafile输出成java文件

  /** Writes this to {@code filer}. */
  public void writeTo(Filer filer) throws IOException {
    String fileName = packageName.isEmpty()
        ? typeSpec.name
        : packageName + "." + typeSpec.name;
    List originatingElements = typeSpec.originatingElements;
    JavaFileObject filerSourceFile = filer.createSourceFile(fileName,
        originatingElements.toArray(new Element[originatingElements.size()]));
    try (Writer writer = filerSourceFile.openWriter()) {
      writeTo(writer);
    } catch (Exception e) {
      try {
        filerSourceFile.delete();
      } catch (Exception ignored) {
      }
      throw e;
    }
  }

在build/generared/source/apt目录下可以找到生成的java文件,这里生成的文件名叫MainActivity_ViewBinding

2、Butterknife的bind方法

在使用Butterknife时,我们需要用Butterknife的bind()方法绑定上下文。看看bind方法做了什么

  @NonNull @UiThread
  //可以看出必须在UI线程调用
  public static Unbinder bind(@NonNull Activity target) {

    //得到activity的decorview
    View sourceView = target.getWindow().getDecorView();
    return 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());

                                                    //重点在这
    Constructorextends Unbinder> constructor = findBindingConstructorForClass(targetClass);

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      //生成constructor实例
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
  }

可以看出调用了findBindingConstructorForClass,并返回了constructor.newInstance(target, source);

看看findBindingConstructorForClass方法

  @Nullable @CheckResult @UiThread
  private static Constructor extends Unbinder> findBindingConstructorForClass(Class cls) {
    Constructorextends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");

      //如果不为空则返回
      return bindingCtor;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      Class bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked

      //如果为空,则反射调用

      bindingCtor = (Constructorextends 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);
    }
//存储结果。
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }

来看看BINDINGS是什么

  @VisibleForTesting
  //class为key,Constructor为value
  static final Map<Class, Constructorextends Unbinder>> BINDINGS = new LinkedHashMap<>();

lass为key,Constructor为value的map

首先会从BINDINGS 中获取对应的Constructor,如果没有获取到,则会进行反射创建。这个类就是我们以前生成的MainActivity_ViewBinding,虽然反射会影响一些性能,但是因为有Binding是存在(一个类只会在第一次反射生成,以后会在Binders中去取)也可以解决一些性能问题。

反射调用后,会将结果存储到BINDINGS 中

3、生成的辅助类分析MainActivity_ViewBinding

ublic class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
  protected T target;
//target是activity,source是activity的Decorview

  @UiThread
  public MainActivity_ViewBinding(T target, View source) {
    this.target = target;
                //重点在这里
    target.tv = Utils.findRequiredViewAsType(source, R.id.tv, "field 'tv'", TextView.class);
  }

  @Override
  @CallSuper
//释放对象,避免内存泄露
  public void unbind() {
    T target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");

    target.tv = null;

    this.target = null;
  }
}

接下来看看Utils.findRequiredViewAsType方法

  public static  T findRequiredViewAsType(View source, @IdRes int id, String who,
      Class cls) {
      //找到view
    View view = findRequiredView(source, id, who);
    //对view进行类型转换
    return castView(view, id, who, cls);
  }

看看findRequiredView

  public static View findRequiredView(View source, @IdRes int id, String who) {

//调用decorview的findviewByID方法,这大家比较熟悉了

    View view = source.findViewById(id);
    if (view != null) {
      return view;
    }

/*
如果找到,直接进行返回
如果没找到,抛出异常
*/
    String name = getResourceEntryName(source, id);
    throw new IllegalStateException("Required view '"
        + name
        + "' with ID "
        + id
        + " for "
        + who
        + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
        + " (methods) annotation.");
  }

接下来查看castView

  public static  T castView(View view, @IdRes int id, String who, Class cls) {
    try {

//对view进行类型转换
      return cls.cast(view);
    } catch (ClassCastException e) {
      String name = getResourceEntryName(view, id);
      throw new IllegalStateException("View '"
          + name
          + "' with ID "
          + id
          + " for "
          + who
          + " was of the wrong type. See cause for more info.", e);
    }
  }

进行类型转换,转换成TextView 。这个返回的TextView会赋值给Target。也就是MainActivity,这样我们就能在MainActivity中使用这个Textview了。

至此,ButterKnife的@bindview基本分析完了。其他注解 的基本流程类同。
最后,总结一下:

ButterKnifeProcessor扫描java代码中的所有ButterKnife注解
ButterKnifeProcessor 根据扫描结果,给对应的activity生成ClassName$$_ViewBinder类
调用Bind方法加载 ViewBinder类,实现动态绑定

在下能力有限,如有错误还请更正~~~~

你可能感兴趣的:(Android,Android框架源码解析)