Android ButterKnife 源码分析

2021-03-17  本文已影响0人  是刘航啊
ButterKnife 依赖
implementation 'com.jakewharton:butterknife:10.2.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
ButterKnife 使用
public class MainActivity extends AppCompatActivity {
    @BindView(R.id.tv_test)
    TextView tvTest;

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

    @OnClick(R.id.tv_test)
    public void tvTestClick(){

    }

}
源码解析 ButterKnife -> bind
public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
}

target.getWindow() 拿到的是 PhoneWindow 对象
sourceView 是 PhoneWindow 通过 getDecorView() 方法拿到顶层的 DecorView
bind() 将当前 Activity 和 DecorView 作为参数传递给 bind()

bind
public static Unbinder bind(@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;
    }
    
    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      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);
    }
}

首先通过 Activity 拿到对应的 Class,其次通过 Class 拿到一个类型为 Unbinder 的 Constructor 构造器对象,最后通过 newInstance 反射得到 Unbinder 对象并返回。

findBindingConstructorForClass
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      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);
    }
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
}

首先会由 BINDINGS ( LinkedHashMap ) 通过 Class 获取对应的 Constructor 构造器对象,如果存在则直接返回

解下来会通过 cls.getName() 获取 Activity 的类名,然后通过反射查找 ClassName_ViewBinding 的类 ( 例如 MainActivity_ViewBinding ) ,并通过这个类找到 Constructor 构造器对象,BINDINGS 加入缓存,最后返回这个构造器对象

MainActivity_ViewBinding
public class MainActivity_ViewBinding implements Unbinder {
    private MainActivity target;
    
    private View view7f08018e;
    @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;
        view = Utils.findRequiredView(source, R.id.tv_test, "field 'tvTest' and method 'tvTestClick'");
        target.tvTest = Utils.castView(view, R.id.tv_test, "field 'tvTest'", TextView.class);
        view7f08018e = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
            target.tvTestClick();
          }
        });
    }
    
    @Override
    @CallSuper
    public void unbind() {
        MainActivity target = this.target;
        if (target == null) throw new IllegalStateException("Bindings already cleared.");
        this.target = null;

        target.tvTest = null;

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

ButterKnife 中 bindingClass.getConstructor(cls, View.class) 对应的就是 MainActivity_ViewBinding 中两个参数的构造方法

 @UiThread
    public MainActivity_ViewBinding(final MainActivity target, View source) {
        this.target = target;
        View view;
        view = Utils.findRequiredView(source, R.id.tv_test, "field 'tvTest' and method 'tvTestClick'");
        target.tvTest = Utils.castView(view, R.id.tv_test, "field 'tvTest'", TextView.class);
        view7f08018e = view;
        view.setOnClickListener(new DebouncingOnClickListener() {
          @Override
          public void doClick(View p0) {
            target.tvTestClick();
          }
        });
    }
Utils.findRequiredView
public static View findRequiredView(View source, @IdRes int id, String who) {
    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.");
}

通过 findViewById 获取对应的 View

Utils.castView
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
      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);
    }
}

将得到的 View 转换成指定的 View

public MainActivity_ViewBinding(final MainActivity target, View source) {
    ...
    View view;
    view = Utils.findRequiredView(source, R.id.tv_test, "field 'tvTest' and method 'tvTestClick'");
    target.tvTest = Utils.castView(view, R.id.tv_test, "field 'tvTest'", TextView.class);
    ...
}

通过 findRequiredView 方法拿到 R.id.tv_test 的 View,然后通过 castView 将 View 转换成 TextVIew

MainActivity_ViewBinding 是如何创建的?首先我们需要了解 2 个知识点
  1. 注解( annotation )
  2. 注解处理器( annotationProcessor )
注解( annotation )

注解其实并不复杂,例如( @A「 A 洗脚店 」、@B「 B 洗脚店」与 @Target「使用范围:方法、变量等」、@Retention「生命周期:类、源文件等」)类似一个标签表明它的作用。给大家推荐一篇文章 注解( annotation )

注解处理器( annotationProcessor )

annotationProcessor 可以理解成一种处理注解的工具( 扫描检测 ),处理的核心就是 AbstractProcessor 。ButterKnife 中处注解的类为 ButterKnifeProcessor

如何查看 ButterKnifeProcessor 类 ?
  1. 在 gradle 中将 annotationProcessor 改为 implementation,即可在 External Libraries 中查看源码
  2. 在 GitHub 中查看源码 ButterKnifeProcessor
ButterKnifeProcessor
@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
    ...
    @Override public synchronized void init(ProcessingEnvironment env) { 
        ...
    }

    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        ...
    }

    @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        ...
    }
    ...
}
ButterKnifeProcessor -> init
@Override public synchronized void init(ProcessingEnvironment env) {
    super.init(env);
    
    String sdk = env.getOptions().get(OPTION_SDK_INT);
    if (sdk != null) {
      try {
        this.sdk = Integer.parseInt(sdk);
      } catch (NumberFormatException e) {
        env.getMessager()
            .printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '"
                + sdk
                + "'. Falling back to API 1 support.");
      }
    }

    debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE));
    
    typeUtils = env.getTypeUtils();
    filer = env.getFiler();
    try {
      trees = Trees.instance(processingEnv);
    } catch (IllegalArgumentException ignored) {
      try {
        // Get original ProcessingEnvironment from Gradle-wrapped one or KAPT-wrapped one.
        for (Field field : processingEnv.getClass().getDeclaredFields()) {
          if (field.getName().equals("delegate") || field.getName().equals("processingEnv")) {
            field.setAccessible(true);
            ProcessingEnvironment javacEnv = (ProcessingEnvironment) field.get(processingEnv);
            trees = Trees.instance(javacEnv);
            break;
          }
        }
      } catch (Throwable ignored2) {
      }
    }
}
ProcessingEnvironment
public interface ProcessingEnvironment {
    //选取重要的解读
    ...
    Elements getElementUtils();
    Types getTypeUtils();
    Filer getFiler();
    ...
}

init 方法主要用来初始化工具类

ButterKnifeProcessor -> getSupportedAnnotations
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;
}

getSupportedAnnotations 返回支持注解的类型

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, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
}
process -> findAndParseTargets
private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    ...
   
    // 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 处理了每个自定义的注解,虽然方法不一样,但是处理的逻辑是相同的,这里抽取 BindView 来分析

findAndParseTargets -> parseBindView(上部分)
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      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();
    }
    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;
    }
    ...
}
findAndParseTargets -> parseBindView -> isInaccessibleViaGeneratedCode
private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
      String targetThing, Element element) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify field or method modifiers.
    Set<Modifier> modifiers = element.getModifiers();
    if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
      error(element, "@%s %s must not be private or static. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    // Verify containing type.
    if (enclosingElement.getKind() != CLASS) {
      error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    // Verify containing class visibility is not private.
    if (enclosingElement.getModifiers().contains(PRIVATE)) {
      error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
          annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
          element.getSimpleName());
      hasError = true;
    }

    return hasError;
}

验证当前类或者方法是否是 PRIVATE|STATIC,验证父类是否是 Class 且是否是 PRIVATE

findAndParseTargets -> parseBindView -> isBindingInWrongPackage
private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
      Element element) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    String qualifiedName = enclosingElement.getQualifiedName().toString();

    if (qualifiedName.startsWith("android.")) {
      error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
          annotationClass.getSimpleName(), qualifiedName);
      return true;
    }
    if (qualifiedName.startsWith("java.")) {
      error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
          annotationClass.getSimpleName(), qualifiedName);
      return true;
    }

    return false;
}

验证当前方法是否以「 android. 」|「 java. 」开头

以上很多验证信息都是验证 Element 是否继承自 View,如果不是继承自View 就不会走后面的流程
findAndParseTargets -> parseBindView(下部分)
private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
      Set<TypeElement> erasedTargetNames) {
    
    ...
    // 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);
    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));

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
}

element.getAnnotation(BindView.class).value() 通过@BindView 注解获取到对应 View 的 id。通过 builderMap.get(enclosingElement) 根据我们对应的元素查找 Builder。如果存在,就会判断有没有重复的引用;如果不存在,就会去创建一个新的 Builder。最后通过 builder.addField() 添加到集合中

findAndParseTargets -> parseBindView -> getOrCreateBindingBuilder
private BindingSet.Builder getOrCreateBindingBuilder(
      Map<TypeElement, BindingSet.Builder> builderMap, TypeElement enclosingElement) {
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    if (builder == null) {
      builder = BindingSet.newBuilder(enclosingElement);
      builderMap.put(enclosingElement, builder);
    }
    return builder;
}
getOrCreateBindingBuilder -> newBuilder
static Builder newBuilder(TypeElement enclosingElement) {
    TypeMirror typeMirror = enclosingElement.asType();

    boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE);
    boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE);
    boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE);

    TypeName targetType = TypeName.get(typeMirror);
    if (targetType instanceof ParameterizedTypeName) {
      targetType = ((ParameterizedTypeName) targetType).rawType;
    }

    ClassName bindingClassName = getBindingClassName(enclosingElement);

    boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    return new Builder(targetType, bindingClassName, enclosingElement, isFinal, isView, isActivity,
        isDialog);
}
newBuilder -> getBindingClassName
static ClassName getBindingClassName(TypeElement typeElement) {
    String packageName = getPackage(typeElement).getQualifiedName().toString();
    String className = typeElement.getQualifiedName().toString().substring(
            packageName.length() + 1).replace('.', '$');
    return ClassName.get(packageName, className + "_ViewBinding");
}

BindingSet 就确定了生成 MainActivity_ViewBinding 类的信息

findAndParseTargets -> parseBindView -> addField
void addField(Id id, FieldViewBinding binding) {
    getOrCreateViewBindings(id).setFieldBinding(binding);
}
addField -> getOrCreateViewBindings
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;
}

最终将 id 与 view 进行绑定,加入集合,findAndParseTargets 的流程就结束了

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, debuggable);
      try {
        javaFile.writeTo(filer);
      } catch (IOException e) {
        error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
      }
    }

    return false;
}
process -> brewJava
JavaFile brewJava(int sdk, boolean debuggable) {
    TypeSpec bindingConfiguration = createType(sdk, debuggable);
    return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
        .addFileComment("Generated code from Butter Knife. Do not modify!")
        .build();
}
brewJava -> createType
private TypeSpec createType(int sdk, boolean debuggable) {
    TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName())
        .addModifiers(PUBLIC)
        .addOriginatingElement(enclosingElement);
    if (isFinal) {
      result.addModifiers(FINAL);
    }

    if (parentBinding != null) {
      result.superclass(parentBinding.getBindingClassName());
    } else {
      result.addSuperinterface(UNBINDER);
    }

    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, debuggable));

    if (hasViewBindings() || parentBinding == null) {
      result.addMethod(createBindingUnbindMethod(result));
    }

    return result.build();
}
if (parentBinding != null) {
    result.superclass(parentBinding.getBindingClassName());
} else {
    result.addSuperinterface(UNBINDER);
}

这段代码的意思就是如果存在父类就继承父类,如果不存在就实现 Unbinder 接口

public class MainActivity_ViewBinding implements Unbinder {

}

上面的方法大致生成这段 Java 代码,下面重点看 createBindingConstructor() 创建绑定的构造器方法

createBindingConstructor
private MethodSpec createBindingConstructor(int sdk, boolean debuggable) {
    ...
    for (ViewBinding binding : viewBindings) {
        addViewBinding(constructor, binding, debuggable);
    }
    ...
}
addViewBinding
private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) {
    ...  
    boolean requiresCast = requiresCast(fieldBinding.getType());
      if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) {
        if (requiresCast) {
          builder.add("($T) ", fieldBinding.getType());
        }
        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;
    }
    ...
}
static boolean requiresCast(TypeName type) {
    return !VIEW_TYPE.equals(type.toString());
}

测试代码中创建的为 TextView,所以 requiresCast 会返回 true,那么会执行 else 部分的代码

完整的字符串就是 "UTILS.find" + "RequiredView" + " (source,R.id.tv_test" +", Description" +")" -> Utils.findRequiredView(source, R.id.tv_test, "Description"),MainActivity_ViewBinding 对应的就是 Utils.findRequiredView(source, R.id.tv_test, "field 'tvTest' and method 'tvTestClick'");

process 流程就分析到这里
参考文献

ButterKnife 原理解析

总结
  1. ButterKnife 通过注解处理器( annotationProcessor ),经过 ButterKnifeProcessor
  1. 在 Activity 中使用 ButterKnife.bind(this) 方法,加载对应的 _ViewBinding 类,达道省去写 findViewById 的目的。
ButterKnife 源码解析就介绍到这里了,如果有什么写得不对的,可以在下方评论留言,我会第一时间改正。
上一篇下一篇

猜你喜欢

热点阅读