ButterKnife源码解析


ButterKnife源码解析_第1张图片
image.png

序言

继上次介绍了EventBus之后,今天我们来介绍另外一个好用的三方库ButterKnife,一个依赖注入框架。该库的作者是被称为Android之神的JakeWarton,我们所熟知的OKHttp、Retrofit、ButterKnife、RxAndroid、RxBinding都是他的杰作。
在使用ButterKnife之前,我们写过最多的代码大概是:

TextView tv = (TextView) findViewById(R.id.tv);

而有了ButterKnife之后,我们就可以告别findViewById(),ButterKnife会自动帮我们生成这些代码。接下来,抱着膜拜大神的态度,让我们一起学习ButterKnife。

ButterKnife的使用

ButterKnife的使用流程如下:

  1. 在工程目录下的build.gradle文件中添加ButterKnife插件路径
classpath "com.jakewharton:butterknife-gradle-plugin:8.6.0"
  1. 在module的build.gradle文件中添加依赖和注解解析器:
api 'com.jakewharton:butterknife:8.8.1'
kapt 'com.jakewharton:butterknife-compiler:8.6.0'
  1. 接下来在Activity或者Fragment的onCreate/onCreateView方法中绑定当前对象:
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Unbinder binder = ButterKnife.bind(this);
}
  1. 然后在需要注入的View上加注解,此处以BindView注解为例:
@BindView(R.id.rbStock)
RadioButton rbStock;
//省略其他控件
  1. 现在,就可以在代码中使用这些控件,而不需要通过findViewById初始化这些控件。
  2. 最后,在Activity的onDestroy方法中从ButterKnife解绑:
@Override
protected void onDestroy() {
    super.onDestroy();
    binder.unbind();
}

到这里,ButterKnife的使用方法介绍完了,可以看到使用过程比较简单。

源码分析

1. ButterKnife绑定Activity/Fragment

我们从ButterKnife绑定Activity入手,来分析原理。
这里,我们用ButterKnife绑定项目的MainActivity:

ButterKnife.bind(this);

跟踪ButterKnife的bind方法:

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

这里会获取Activity对应Window的DecorView,然后调用createBinding方法:

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 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);
    }
}

着重看一下findBindingConstructorForClass方法:

@Nullable @CheckResult @UiThread
private static Constructor findBindingConstructorForClass(Class cls) {
    Constructor 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 = (Constructor) 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;
}

该方法接收一个Class对象,即我们的MainActivity对应的Class对象。首先会从BINDINGS中寻找是否存在构造器,BINDINGS定义如下:

@VisibleForTesting
static final Map, Constructor> BINDINGS = new LinkedHashMap<>();

它是一个Map,键是一个Class对象,值是一个构造器,至于是什么的构造器下面会揭晓答案。
继续上面的分析,如果在BINDINGS中找到构造器,则直接返回构造器。如果没有找到,就校验这个Class对象是否合法,如果合法,就执行下面一句代码:

Class bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");

加载一个名为MainActivity_ViewBinding的类,并且获取它的构造器,并保存在BINDINGS中,下次再bind这个类的时候,就直接从BINDINGS中获取构造器,提升性能。
MainActivity_ViewBinding是通过APT(编译时解析技术)生成的类。后面会介绍具体的生成过程。

再回到createBinding方法,获取到MainActivity_ViewBinding的构造器之后,会调用构造器的newInstance方法构建一个MainActivity_ViewBinding对象。我们看一下MainActivity_ViewBinding的定义:

public class MainActivity_ViewBinding implements Unbinder {
    private MainActivity target;
    
    @UiThread
    public MainActivity_ViewBinding(MainActivity target) {
      this(target, target.getWindow().getDecorView());
    }
    
    @UiThread
    public MainActivity_ViewBinding(MainActivity target, View source) {
      this.target = target;
    
      target.rbStock = Utils.findRequiredViewAsType(source, R.id.rbStock, "field 'rbStock'", RadioButton.class);
      //省略其他控件初始化代码
    }
    
    @Override
    @CallSuper
    public void unbind() {
      MainActivity target = this.target;
      if (target == null) throw new IllegalStateException("Bindings already cleared.");
      this.target = null;
    
      target.rbStock = null;
      //省略重复其他控置空代码
    }
}

这个类的构造方法中初始化了带@BindView注解的控件,我们看一下具体初始化过程:

public static  T findRequiredViewAsType(View source, @IdRes int id, String who,
      Class cls) {
    View view = findRequiredView(source, id, who);
    return castView(view, id, who, cls);
}

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.");
}

在findRequiredView方法中,我们看见了熟悉的findViewById方法,到这里相信大家都清楚了:ButterKnife的原理是通过APT技术在编译过程生成了一个XX_ViewBinding类,之后在绑定的时候,会创建XX_ViewBinding对象,而XX_ViewBinding的构造方法中会帮我们出初始化这些注解修饰的控件

MainActivity_ViewBinding实现了Unbinder接口,我们看一下接口定义:

public interface Unbinder {
    @UiThread void unbind();
    
    Unbinder EMPTY = new Unbinder() {
      @Override public void unbind() { }
    };
}

很简单,只有一个unbind方法,而MainActivity_ViewBinding在unbind方法中把注解对应的控件和target都置为null,释放资源。就是使用过程第6步在onDestroy中会调用该方法。

2. 编译时生成_ViewBinding类

接下来,我们看一下怎样通过APT技术生成MainActivity_ViewBinding类。了解APT的同学会知道,主要是通过AbstractProcessor类在编译过程中处理注解。
下面,我们就看一下ButterKnife对应注解处理类ButterKnifeProcessor

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {
    private static final String OPTION_SDK_INT = "butterknife.minSdk";
    static final Id NO_ID = new Id(-1);
    static final String VIEW_TYPE = "android.view.View";
    static final String ACTIVITY_TYPE = "android.app.Activity";
    static final String DIALOG_TYPE = "android.app.Dialog";
    private static final String COLOR_STATE_LIST_TYPE = "android.content.res.ColorStateList";
    private static final String BITMAP_TYPE = "android.graphics.Bitmap";
    private static final String DRAWABLE_TYPE = "android.graphics.drawable.Drawable";
    private static final String TYPED_ARRAY_TYPE = "android.content.res.TypedArray";
    private static final String NULLABLE_ANNOTATION_NAME = "Nullable";
    private static final String STRING_TYPE = "java.lang.String";
    private static final String LIST_TYPE = List.class.getCanonicalName();
    private static final List> LISTENERS = Arrays.asList(OnCheckedChanged.class, OnClick.class, OnEditorAction.class, OnFocusChange.class, OnItemClick.class, OnItemLongClick.class, OnItemSelected.class, OnLongClick.class, OnPageChange.class, OnTextChanged.class, OnTouch.class);
    private static final List SUPPORTED_TYPES = Arrays.asList("array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "string");
    private Elements elementUtils;
    private Types typeUtils;
    private Filer filer;
    private Trees trees;
    private int sdk = 1;
    private final Map symbols = new LinkedHashMap();

    public ButterKnifeProcessor() {
    }

    //初始化一些参数和工具类
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);
        String sdk = (String)env.getOptions().get("butterknife.minSdk");
        if (sdk != null) {
            try {
                this.sdk = Integer.parseInt(sdk);
            } catch (NumberFormatException var5) {
                env.getMessager().printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '" + sdk + "'. Falling back to API 1 support.");
            }
        }

        this.elementUtils = env.getElementUtils();
        this.typeUtils = env.getTypeUtils();
        this.filer = env.getFiler();

        try {
            this.trees = Trees.instance(this.processingEnv);
        } catch (IllegalArgumentException var4) {
            ;
        }

    }

    //获取该Processor能处理的注解集合
    private Set> getSupportedAnnotations() {
        Set> annotations = new LinkedHashSet();
        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(BindInt.class);
        annotations.add(BindString.class);
        annotations.add(BindView.class);
        annotations.add(BindViews.class);
        annotations.addAll(LISTENERS);
        return annotations;
    }

    //主要的处理方法,分析将从这里开始
    public boolean process(Set elements, RoundEnvironment env) {
        Map bindingMap = this.findAndParseTargets(env);
        Iterator var4 = bindingMap.entrySet().iterator();

        while(var4.hasNext()) {
            Entry entry = (Entry)var4.next();
            TypeElement typeElement = (TypeElement)entry.getKey();
            BindingSet binding = (BindingSet)entry.getValue();
            JavaFile javaFile = binding.brewJava(this.sdk);

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

        return false;
    }
    
    //省略很多方法
}
    

我们就直接从process方法开始分析,这个方法的逻辑比较简单,通过findAndParseTargets方法找到所有使用ButterKnife的类,然后根据注解信息生成对应的_ViewBinding类
可以把这段代码分为两个过程:处理注解并记录信息生成_ViewBinding类

a. 处理注解并记录信息

先看一下findAndParseTargets方法:

private Map findAndParseTargets(RoundEnvironment env) {
    Map builderMap = new LinkedHashMap();
    Set erasedTargetNames = new LinkedHashSet();
    this.scanForRClasses(env);

    //省略处理其他注解的代码
    
    var4 = env.getElementsAnnotatedWith(BindView.class).iterator();

    while(var4.hasNext()) {
        element = (Element)var4.next();

        try {
            this.parseBindView(element, builderMap, erasedTargetNames);
        } catch (Exception var12) {
            this.logParsingError(element, BindView.class, var12);
        }
    }

    //省略处理其他注解的代码

    var4 = LISTENERS.iterator();

    while(var4.hasNext()) {
        Class listener = (Class)var4.next();
        this.findAndParseListener(env, listener, builderMap, erasedTargetNames);
    }

    Deque> entries = new ArrayDeque(builderMap.entrySet());
    LinkedHashMap bindingMap = new LinkedHashMap();

    while(!entries.isEmpty()) {
        Entry entry = (Entry)entries.removeFirst();
        TypeElement type = (TypeElement)entry.getKey();
        Builder builder = (Builder)entry.getValue();
        TypeElement parentType = this.findParentType(type, erasedTargetNames);
        if (parentType == null) {
            bindingMap.put(type, builder.build());
        } else {
            BindingSet parentBinding = (BindingSet)bindingMap.get(parentType);
            if (parentBinding != null) {
                builder.setParent(parentBinding);
                bindingMap.put(type, builder.build());
            } else {
                entries.addLast(entry);
            }
        }
    }

    return bindingMap;
}

这个方法很长,主要的作用就是找到项目中所有使用ButterKnife注解的类,分别处理他们,最后把他们的信息保存在一个Map中:

LinkedHashMap bindingMap = new LinkedHashMap();

这个Map以ElementType(使用注解的类)为key,值则是一个BindingSet对象(记录了类中用到的注解及作用对象等信息)。后面就是通过这个Map生成对应的_ViewBinding类。
接下来,我们分析一下注解BindView的处理过程,主要是通过parseBindView方法处理:

private void parseBindView(Element element, Map builderMap, Set erasedTargetNames) {
    TypeElement enclosingElement = (TypeElement)element.getEnclosingElement();
    //检查是否满足使用注解的条件
    boolean hasError = this.isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || this.isBindingInWrongPackage(BindView.class, element);
    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, "android.view.View") && !this.isInterface(elementType)) {
        if (elementType.getKind() == TypeKind.ERROR) {
            this.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 {
            this.error(element, "@%s fields must extend from View or be an interface. (%s.%s)", BindView.class.getSimpleName(), qualifiedName, simpleName);
            hasError = true;
        }
    }
    //满足条件,开始解析注解
    if (!hasError) {
        int id = ((BindView)element.getAnnotation(BindView.class)).value();
        //从缓存builderMap中查找注解使用类对应的builder对象
        Builder builder = (Builder)builderMap.get(enclosingElement);
        QualifiedId qualifiedId = this.elementToQualifiedId(element, id);
        String existingBindingName;
        if (builder != null) {
            existingBindingName = builder.findExistingBindingName(this.getId(qualifiedId));
            if (existingBindingName != null) {
                this.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对象,则创建这个对象,并且保存在builderMap中
            builder = this.getOrCreateBindingBuilder(builderMap, enclosingElement);
        }

        existingBindingName = simpleName.toString();
        TypeName type = TypeName.get(elementType);
        boolean required = isFieldRequired(element);
        //把注解BindView修饰的字段添加到builder中
        builder.addField(this.getId(qualifiedId), new FieldViewBinding(existingBindingName, type, required));
        erasedTargetNames.add(enclosingElement);
    }
}

代码中关键的地方给出了注释,各位同学可以根据注释自行理解代码。其他注解的处理方式跟注解BindView类似,这里不做介绍。就这样,把类和它对应的注解信息都保存到Map中。

b. 生成_ViewBinding类

我们再来看一下process方法:

public boolean process(Set elements, RoundEnvironment env) {
    Map bindingMap = this.findAndParseTargets(env);
    Iterator var4 = bindingMap.entrySet().iterator();

    while(var4.hasNext()) {
        Entry entry = (Entry)var4.next();
        TypeElement typeElement = (TypeElement)entry.getKey();
        BindingSet binding = (BindingSet)entry.getValue();
        JavaFile javaFile = binding.brewJava(this.sdk);

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

    return false;
}

通过第一步得到了bindingMap对象,接下来遍历bindingMap,通过BindingSet的brewJava方法生成对应的ViewBinding类:

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

static BindingSet.Builder newBuilder(TypeElement enclosingElement) {
    TypeMirror typeMirror = enclosingElement.asType();
    boolean isView = ButterKnifeProcessor.isSubtypeOfType(typeMirror, "android.view.View");
    boolean isActivity = ButterKnifeProcessor.isSubtypeOfType(typeMirror, "android.app.Activity");
    boolean isDialog = ButterKnifeProcessor.isSubtypeOfType(typeMirror, "android.app.Dialog");
    TypeName targetType = TypeName.get(typeMirror);
    if (targetType instanceof ParameterizedTypeName) {
        targetType = ((ParameterizedTypeName)targetType).rawType;
    }

    String packageName = MoreElements.getPackage(enclosingElement).getQualifiedName().toString();
    String className = enclosingElement.getQualifiedName().toString().substring(packageName.length() + 1).replace('.', '$');
    ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding", new String[0]);
    boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
    return new BindingSet.Builder((TypeName)targetType, bindingClassName, isFinal, isView, isActivity, isDialog);
}

这里就不深入介绍生成ViewBinding类的过程,它使用到了javapoet技术,javapoet技术是square公司开源的,专门用来生成java源码,很多使用APT技术的库都是使用到javapoet技术。

总结

到这里,ButterKnife的原理我们就介绍完了,各位同学按照本文思路,对着ButterKnife源码进行学习。
如果各位同学使用过kotlin,就知道kotlin中不需要ButterKnife,kotlin提供了插件,可以直接获取xml中的控件,还提供了一个功能强大又易用的库anko,有兴趣的小伙伴可以了解一下。

你可能感兴趣的:(ButterKnife源码解析)