简谈源码-ButterKnife(v10.2.3)

ButterKnife官网

用法示例

package com.example.butterknife.library;
public class SimpleActivity extends Activity {

    @BindView(R.id.hello) Button hello;
    @BindView(R.id.titleTv) TextView titleTv;

    @OnClick(R.id.hello)
    void sayHello() {
        Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show();
    }

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

        hello.setText("你好");
    }
}

ButterKnife.bind(this)源码

绑定activity实例,通过实例关联apt生成的对应的activity_ViewBinding类,通过顶层View关联包含的控件

@NonNull
@UiThread
public static Unbinder bind(@NonNull Activity target) {
    //获取当前页面的顶层视图
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
}

@NonNull
@UiThread
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());
    //targetClass如com.example.butterknife.library.SimpleActivity
    //查找包+类名+_ViewBinding的构造函数
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);

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

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
        //通过构造函数如public com.example.butterknife.library.SimpleActivity_ViewBinding(com.example.butterknife.library.SimpleActivity, android.view.View)
        //创建SimpleActivity_ViewBinding实例
        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);
    }
}

找出_ViewBinding的构造方法

@Nullable
@CheckResult
@UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    //缓存中获取_ViewBinding类的构造函数
    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;
    }

    //获取包类路径[如com.example.butterknife.library.SimpleActivity]
    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 {
        //加载在编译期生成的_ViewBinding类[如SimpleActivity_ViewBinding]
        Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
        //noinspection unchecked
        //获取_ViewBinding类的构造函数[如public SimpleActivity_ViewBinding(final SimpleActivity target, View source) {}]
        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);
    }
    //_ViewBinding类和对应构造函数置入缓存
    //如页面A反复创建销毁多次后再创建进入bind(this)方法可以从缓存中获取对应的A_ViewBinding类
    //如(com.example.butterknife.library.SimpleActivity, public com.example.butterknife.library.SimpleActivity_ViewBinding(com.example.butterknife.library.SimpleActivity, android.view.View))
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
}

apt生成的_ViewBinding代码示例

apt处理后生成的和activity关联的_ViewBinding类,管理了activity内的各个控件,变量和控件id仍是通过findViewById关联

// Generated code from Butter Knife. Do not modify!
package com.example.butterknife.library;
public class SimpleActivity_ViewBinding implements Unbinder {
  private SimpleActivity target;

  private View view7f05000a;

  @UiThread
  public SimpleActivity_ViewBinding(SimpleActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public SimpleActivity_ViewBinding(final SimpleActivity target, View source) {
    this.target = target;

    View view;
    //findRequiredView就是用findViewById关联变量和控件id
    view = Utils.findRequiredView(source, R.id.hello, "field 'hello' and method 'sayHello'");
    target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);
    view7f05000a = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.sayHello();
      }
    });
    target.titleTv = Utils.findRequiredViewAsType(source, R.id.titleTv, "field 'titleTv'", TextView.class);
  }

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

    target.hello = null;
    target.titleTv = null;

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

apt核心流程源码

TypeElement如SimpleActivity,BindingSet如hello/titleTv等控件信息,根据bindingMap写_ViewBinding文件,该文件包含页面和页面内的控件集合信息,参考上面生成的文件

@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //map存的是类和类中被ButterKnife注解的元素及相关信息
    //如SimpleActivity有@BindView(R.id.hello) Button hello; @BindView(R.id.titleTv) TextView titleTv;等
    //XXActivity有@BindView(R.id.tv) TextView tv; @BindView(R.id.bt) Button bt;等
    Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) {
        TypeElement typeElement = entry.getKey();
        BindingSet binding = entry.getValue();

        //将生成的类和类对应的控件集合等信息,添加必要的构造类的语句,写入java文件
        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;
}

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    //BindingSet为将要写入java文件的控件集合信息
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    //...@BindAnim/@BindArray等处理,参考下面@BindView逻辑

    // Process each @BindView element.
    //处理被@BindView注解的元素,处理拼接记录信息以生成代码写入Filer
    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);
        }
    }

    //...

    //参考process方法内的注释
    //就是从builderMap,erasedTargetNames中获取将要生成代码的信息,在处理一下存储至bindingMap返回
    Map<TypeElement, ClasspathBindingSet> classpathBindings =
            findAllSupertypeBindings(builderMap, erasedTargetNames);

    // Associate superclass binders with their subclass binders. This is a queue-based tree walk
    // which starts at the roots (superclasses) and walks to the leafs (subclasses).
    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();

        TypeElement parentType = findParentType(type, erasedTargetNames, classpathBindings.keySet());
        if (parentType == null) {
            bindingMap.put(type, builder.build());
        } else {
            BindingInformationProvider parentBinding = bindingMap.get(parentType);
            if (parentBinding == null) {
                parentBinding = classpathBindings.get(parentType);
            }
            if (parentBinding != null) {
                builder.setParent(parentBinding);
                bindingMap.put(type, builder.build());
            } else {
                // Has a superclass binding but we haven't built it yet. Re-enqueue for later.
                entries.addLast(entry);
            }
        }
    }

    return bindingMap;
}

private void parseBindView(Element element, Map<TypeElement, BindingSet.Builder> builderMap,
                           Set<TypeElement> erasedTargetNames) {
    //enclosingElement为element的父元素[如MainActivity]
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Start by verifying common generated code restrictions.
    //检查元素
    //isInaccessibleViaGeneratedCode=>element不是private或static修饰,且enclosingElement是class且不被private修饰
    //isBindingInWrongPackage=>enclosingElement类不在android.或java.系统包下
    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();
    //检查元素类型不是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;
    }

    // Assemble information on the field.
    //收集element信息[id如xml中的R.id.tv]
    int id = element.getAnnotation(BindView.class).value();
    //收集enclosingElement内的所有将要绑定的控件id信息[存于BindingSet中]
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    Id resourceId = elementToId(element, BindView.class, id);
    if (builder != null) {
        //检查builderMap已包含该控件id
        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 {
        //如存储SimpleActivity中控件信息的BindingSet为空则初始化
        builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }

    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    //检查元素是否被@Nullable注解声明
    boolean required = isFieldRequired(element);

    //将当前控件信息和控件id存入BindingSet中
    builder.addField(resourceId, new FieldViewBinding(name, type, required));

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

概括

apt根据被@BindView注解的元素,获取相关信息生成java文件;如根据@BindView(R.id.hello)生成了[SimpleActivity, hello控件id及相关信息],此时又处理@BindView(R.id.titleTv),通过process方法/parseBindView方法生成了[SimpleActivity, hello控件id及相关信息, titleTv控件id及相关系],将这些信息存进bindingMap中,bindingMap的BindingSet中存储了控件的相关信息[可以自行查看BindingSet源码,会清楚很多],完了就把这些activity和对应的控件信息写到_ViewBinding文件中;

activity中bind(this)后,通过this实例找到_ViewBinding构造方法,加载activity_ViewBinding类,在activity_ViewBinding中控件仍是通过findViewById来关联的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值