• ButterKnife的使用及其解析


    本博客介绍ButterKnife的使用及其源码解析。

    ButterKnife的使用

    ButterKnife简介

    添加依赖

    在Project级别的build.gradle文件中添加为ButterKnife定制的Gradle插件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    buildscript {
    repositories {
    jcenter()
    }
    dependencies {
    classpath 'com.android.tools.build:gradle:2.3.3'
    classpath 'com.jakewharton:butterknife-gradle-plugin:8.8.1'
    }
    }

    在Application级别的build.gradle文件中添加ButterKnife插件和依赖代码:

    1
    2
    3
    4
    5
    6
    apply plugin: 'com.jakewharton.butterknife'
    ...
    dependencies{
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
    compile 'com.android.support:recyclerview-v7:26.0.0-alpha1'
    }

    使用ButterKnife

    • 使用ButterKnife绑定控件

    用注解@BindView()绑定单个控件id,用注解@BindViews()绑定多个控件id:

    1
    2
    3
    4
    5
    6
    (R.id.et_input)
    EditText etInput;
    (R.id.btn_send)
    Button btnSend;
    (R.id.lv_main)
    ListView lvMain;

    在Activity中,需要在setContentView()之后添加:

    1
    mUnbinder = ButterKnife.bind(this);

    切记在Activity的onDestroy()中进行解绑:

    1
    mUnbinder.unbind();

    在Fragment中,需要在onCreateView()的return之前添加:

    1
    mUnbinder = ButterKnife.bind(this, view);

    在Fragment的onDestroyView()中进行以下操作:

    1
    mUnbinder.unbind();

    而在ListView或者RecyclerView的Adapter中也可以使用ButterKnife,需要做以下操作:

    1
    2
    3
    4
    public (View itemView) {
    super(itemView);
    ButterKnife.bind(this, itemView);
    }

    • 使用ButterKnife绑定资源

    ButterKnife提供了以下资源绑定方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @BindAnim()
    //2、绑定数组资源id
    @BindArray()
    //3、绑定Bitmap相关的资源id
    @BindBitmap()
    //4、绑定boolean资源id
    @BindBool()
    //5、绑定color资源id
    @BindColor()
    //6、绑定尺寸资源id
    @BindDimen()
    //7、绑定drawable资源id
    @BindDrawable()
    //8、绑定float资源id
    @BindFloat
    //9、绑定字体资源id
    @BindFont
    //10、绑定int资源id
    @BindInt
    //11、绑定String资源id
    @BindString

    • 使用ButterKnife绑定监听

    ButterKnife提供了以下监听绑定方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //1、为添加`OnCheckedChangeListener`的View绑定资源id
    @OnCheckedChanged()
    //2、为添加`OnClickListener`的View绑定资源id
    @OnClick()
    //3、为添加`OnEditorActionListener`的View绑定资源id
    @OnEditorAction()
    //4、为添加`OnFocusChangeListener`的View绑定资源id
    @OnFocusChange()
    //5、为添加`OnItemClickListener`的View绑定资源id
    @OnItemClick
    //6、为添加`OnItemLongClickListener`的View绑定资源id
    @OnItemLongClick()
    //7、为添加`OnItemSelectedListener`的View绑定资源id
    @OnItemSelected()
    //8、为添加`OnLongClickListener`的View绑定资源id
    @OnLongClick()
    //9、为添加`OnPageChangeListener`的View绑定资源id
    @OnPageChange()
    //10、为添加`TextWatcher`的View绑定资源id
    @OnTextChanged()
    //11、为添加`OnTouchListener`的View绑定资源id
    @OnTouch()

    • 使用ButterKnife可选绑定
    1
    2
    3
    4
    //1、防止空指针异常
    @Nullable
    //2、注入指定的视图不需要出现在该视图
    @Optional

    ButterKnife源码解析

    ButterKnife采用的是编译时注解,自定义了很多常用注解,上文已经讲解在此不再赘述。以@BindView注解为例:

    1
    2
    3
    4
    @Retention(CLASS) @Target(FIELD)
    public @interface BindView {
    @IdRes int value();
    }

    其中@Retention(CLASS)用来声明注解的保留策略,表明@BindView注解是编译时注解,而@Target(FIELD)则表明@BindView注解作用于成员变量。

    解析ButterKnife注解处理器ButterKnifeProcessor

    要处理注解需要使用到注解处理器,ButterKnife的注解处理器是ButterKnifeProcessor,继承自AbstractProcessor

    1
    2
    3
    4
    @AutoService(Processor.class)
    public final class ButterKnifeProcessor extends AbstractProcessor {
    ...
    }

    在注解处理器ButterKnifeProcessor中注解处理的主要逻辑都在process()中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //1、查找和解析传入的目标`RoundEnvironment`对象并将其存入Map中
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);
    //2、遍历Map集合
    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
    //3、获取键TypeElement
    TypeElement typeElement = entry.getKey();
    //4、获取值BindingSet
    BindingSet binding = entry.getValue();
    //5、产生JavaFile
    JavaFile javaFile = binding.brewJava(sdk, debuggable);
    try {
    //6、向JavaFile写入Filer
    javaFile.writeTo(filer);
    } catch (IOException e) {
    error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage());
    }
    }
    return false;
    }

    接下来查看在process()中调用的findAndParseTargets()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    //1、创建LinkedHashMap用于存储BindingBuilder
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    //2、创建Set用于存储builderMap键
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
    scanForRClasses(env);
    //3、解析每个@BindAnim注解
    for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) {
    if (!SuperficialValidation.validateElement(element)) continue;
    try {
    //4、动画资源相应的解析方法
    parseResourceAnimation(element, builderMap, erasedTargetNames);
    } catch (Exception e) {
    logParsingError(element, BindAnim.class, e);
    }
    }
    ...(此处省略n多个@BindXxx注解)
    //5、创建ArrayDeque用于存储BindingSet.Builder对象
    Deque<Map.Entry<TypeElement, BindingSet.Builder>> entries =
    new ArrayDeque<>(builderMap.entrySet());
    //6、创建LinkedHashMap用于存储绑定中元素
    Map<TypeElement, BindingSet> bindingMap = new LinkedHashMap<>();
    while (!entries.isEmpty()) {
    //7、移除第一个元素(出队)
    Map.Entry<TypeElement, BindingSet.Builder> entry = entries.removeFirst();
    //8、获取移除元素的键
    TypeElement type = entry.getKey();
    //9、获取移除元素的值
    BindingSet.Builder builder = entry.getValue();
    //10、在提供的Set中找到父binder类型
    TypeElement parentType = findParentType(type, erasedTargetNames);
    if (parentType == null) {
    bindingMap.put(type, builder.build());
    } else {
    BindingSet parentBinding = bindingMap.get(parentType);
    if (parentBinding != null) {
    builder.setParent(parentBinding);
    bindingMap.put(type, builder.build());
    } else {
    //11、入队
    entries.addLast(entry);
    }
    }
    }
    return bindingMap;
    }

    接下来查阅parseResourceAnimation()代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    private void parseResourceAnimation(Element element,
    Map<TypeElement, BindingSet.Builder> builderMap, Set<TypeElement> erasedTargetNames) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    //1、验证目标类型是动画
    if (!ANIMATION_TYPE.equals(element.asType().toString())) {
    error(element, "@%s field type must be 'Animation'. (%s.%s)",
    BindAnim.class.getSimpleName(), enclosingElement.getQualifiedName(),
    element.getSimpleName());
    hasError = true;
    }
    //2、验证常见的生成代码限制
    hasError |= isInaccessibleViaGeneratedCode(BindAnim.class, "fields", element);
    hasError |= isBindingInWrongPackage(BindAnim.class, element);
    if (hasError) {
    return;
    }
    //3、收集信息在成员变量中
    String name = element.getSimpleName().toString();
    int id = element.getAnnotation(BindAnim.class).value();
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
    BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    builder.addResource(new FieldAnimationBinding(getId(qualifiedId), name));
    erasedTargetNames.add(enclosingElement);
    }

    其中,parseResourceAnimation()isInaccessibleViaGeneratedCode()代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    大专栏  ButterKnife的使用及其解析="code">
    private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
    String targetThing, Element element) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    //1、方法修饰符不能为private和static
    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;
    }
    //2、包含类型不能为非class
    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;
    }
    //3、包含类的修饰符不能为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;
    }

    其中,parseResourceAnimation()isBindingInWrongPackage()代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
    Element element) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    String qualifiedName = enclosingElement.getQualifiedName().toString();
    //1、判断类的包名不能以android开头
    if (qualifiedName.startsWith("android.")) {
    error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
    annotationClass.getSimpleName(), qualifiedName);
    return true;
    }
    //2、判断类的包名不能以java开头
    if (qualifiedName.startsWith("java.")) {
    error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
    annotationClass.getSimpleName(), qualifiedName);
    return true;
    }
    return false;
    }

    解析ButterKnife的bind()

    ButterKnife的bind()运行在UI线程,在指定的Activity中注释变量和方法,当前的内容视图被用作根视图。

    1
    2
    3
    4
    5
    6
    7
    @NonNull @UiThread
    public static Unbinder bind(@NonNull Activity target) {
    //1、获取根视图
    View sourceView = target.getWindow().getDecorView();
    //2、创建绑定
    return createBinding(target, sourceView);
    }

    接下来查阅createBinding()代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    //1、获取目标Class
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    //2、为Class查找Binding构造方法
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    if (constructor == null) {
    return Unbinder.EMPTY;
    }
    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
    //3、返回构造方法实例对象
    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()代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    @Nullable @CheckResult @UiThread
    private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    //1、根据传入的Class从Map中获取Constructor对象
    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();
    //2、类名称如果以android或java开头返回null
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
    if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
    return null;
    }
    try {
    //3、生成<类名>_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);
    }
    //4、将Constructor放入Map并返回
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
    }

    而上段代码中多次出现的BINDINGS是什么样的数据结构呢?

    1
    2
    @VisibleForTesting
    static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

    解析ButterKnife生成辅助类

    在上问分析中已经生成了<类名>_ViewBinding的类,下面分析<类名>_ViewBinding类中的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class MainActivity_ViewBinding implements Unbinder {
    private MainActivity target;
    private View view2131427424;
    @UiThread
    public MainActivity_ViewBinding(MainActivity target) {
    //1、构造方法分钟传入了MainActivity的DecorView
    this(target, target.getWindow().getDecorView());
    }
    @UiThread
    public MainActivity_ViewBinding(final MainActivity target, View source) {
    this.target = target;
    View view;
    //2、根据类型查找到需要的View
    target.etInput = Utils.findRequiredViewAsType(source, R.id.et_input, "field 'etInput'", EditText.class);
    //3、查找需要的View
    view = Utils.findRequiredView(source, R.id.btn_send, "field 'btnSend' and method 'OnClick'");
    //4、View类型转换
    target.btnSend = Utils.castView(view, R.id.btn_send, "field 'btnSend'", Button.class);
    view2131427424 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
    @Override
    public void doClick(View p0) {
    target.OnClick();
    }
    });
    }
    @Override
    @CallSuper
    public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;
    target.etInput = null;
    target.btnSend = null;
    view2131427424.setOnClickListener(null);
    view2131427424 = null;
    }
    }

    下面查阅Utils中的findRequiredViewAsType()

    1
    2
    3
    4
    5
    6
    7
    public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
    Class<T> cls) {
    //1、获取需要的View
    View view = findRequiredView(source, id, who);
    //2、View类型转换
    return castView(view, id, who, cls);
    }

    下面查阅Utils中findRequiredView()代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public static View findRequiredView(View source, @IdRes int id, String who) {
    //1、最终还是通过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.");
    }

    下面查阅Utils中castView()代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
    //1、类型转换类似于(TextView)findViewById()
    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);
    }
    }

  • 相关阅读:
    2014年之新年新愿
    C#解析Xml的Dom和Sax方式性能分析
    WCF协议与绑定
    使用SqlServer数据批量插入
    跨站脚本攻击XSS
    疯狂的JSONP
    SQLiteOpenHelper
    Android常用的UI布局
    Android用户界面
    ListView
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12268257.html
Copyright © 2020-2023  润新知