Butterknife原理解析

来源:互联网 发布:李宗仁故居风水数据 编辑:程序博客网 时间:2024/06/08 19:39

转载请标明出处:【顾林海的博客】

前言

Butterknife是一个专注于Android系统的View注入框架,可以简化代码,比如findViewById、事件监听、资源绑定等,同时该框架使用了编译时注解,可能大家一听到编译时注解就认为这种方式会影响性能,其实编译时注解并不会影响应用的性能,这是因为编译时注解是在代码编译过程中对注解进行处理,生成代码,这些代码在运行时调用,除了编译时注解,还有一个是运行时注解,它是在运行过程中,通过反射获取相关类、方法、参数等信息,因此运行时注解会有性能问题。

原理解析

平时使用Butterknife时,需要调用Butterknife的bind方法,Butterknife提供以下几种bind方法:

这里写图片描述

上面六种方法最终都会调用createBinding方法,这里以bind(Activity)代码为例:

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

方法中通过传入的Activity获取DecorView,DecorView是整个View树的顶层View,内部包含标题栏和ContentView,而ContentView内部就是我们定义的视图View,拿到DecorView后调用用createBinding方法并把目标Activity和DecorView传入过去。

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

方法中获取目标Class,并通过findBindingConstructorForClass方法获取构造函数,findBindingConstructorForClass方法代码如下:

@Nullable @CheckResult @UiThreadprivate static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {  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();  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<? 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是一个Map,用于缓存,方便下次使用时之间从集合中取出,如果集合中没有,会加载当前的class的名字加上_ViewBinding,比如MainActivity_ViewBinding,加载并获取该class,在获取它的两个参数的构造函数,最后将加载的class进行缓存存入BINDINGS集合中。之后通过createBinding方法中的newInstance进行实例化。

从上面Butterknife执行的绑定方法就可以知道先去加载classname+_ViewBinding的类,并进行实例化,但这个类我们并没有编写,是自动生成的,也就是编译时生成,编译时注解时需要AbstractProcessor这个类来实现,需要重写它的process方法,比如下面:

public class TestProcessor extends AbstractProcessor {    @Override    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {        // TODO Auto-generated method stub return false; } }    }}

在process方法中可以扫描和处理注解的代码,并会生成相关的Java文件,查看Butterknife中继承了AbstractProcessor的类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;}

findAndParseTargts是用于处理@BindViewXX注解的方法,内部有多个循环代码,这些for循环的作用是对注解进行处理。

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();    scanForRClasses(env);    // Process each @BindAnim element.    for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) {        if (!SuperficialValidation.validateElement(element)) continue;        try {            parseResourceAnimation(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindAnim.class, e);        }    }    // Process each @BindArray element.    for (Element element : env.getElementsAnnotatedWith(BindArray.class)) {        if (!SuperficialValidation.validateElement(element)) continue;        try {            parseResourceArray(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindArray.class, e);        }    }    // Process each @BindBitmap element.    for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) {        if (!SuperficialValidation.validateElement(element)) continue;        try {            parseResourceBitmap(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindBitmap.class, e);        }    }    // Process each @BindBool element.    for (Element element : env.getElementsAnnotatedWith(BindBool.class)) {        if (!SuperficialValidation.validateElement(element)) continue;        try {            parseResourceBool(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindBool.class, e);        }    }    // Process each @BindColor element.    for (Element element : env.getElementsAnnotatedWith(BindColor.class)) {        if (!SuperficialValidation.validateElement(element)) continue;        try {            parseResourceColor(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindColor.class, e);        }    }    // Process each @BindDimen element.    for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) {        if (!SuperficialValidation.validateElement(element)) continue;        try {            parseResourceDimen(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindDimen.class, e);        }    }    // Process each @BindDrawable element.    for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) {        if (!SuperficialValidation.validateElement(element)) continue;        try {            parseResourceDrawable(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindDrawable.class, e);        }    }    // Process each @BindFloat element.    for (Element element : env.getElementsAnnotatedWith(BindFloat.class)) {        if (!SuperficialValidation.validateElement(element)) continue;        try {            parseResourceFloat(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindFloat.class, e);        }    }    // Process each @BindFont element.    for (Element element : env.getElementsAnnotatedWith(BindFont.class)) {        if (!SuperficialValidation.validateElement(element)) continue;        try {            parseResourceFont(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindFont.class, e);        }    }    // Process each @BindInt element.    for (Element element : env.getElementsAnnotatedWith(BindInt.class)) {        if (!SuperficialValidation.validateElement(element)) continue;        try {            parseResourceInt(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindInt.class, e);        }    }    // Process each @BindString element.    for (Element element : env.getElementsAnnotatedWith(BindString.class)) {        if (!SuperficialValidation.validateElement(element)) continue;        try {            parseResourceString(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindString.class, e);        }    }    // 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);        }    }    // Process each @BindViews element.    for (Element element : env.getElementsAnnotatedWith(BindViews.class)) {        // we don't SuperficialValidation.validateElement(element)        // so that an unresolved View type can be generated by later processing rounds        try {            parseBindViews(element, builderMap, erasedTargetNames);        } catch (Exception e) {            logParsingError(element, BindViews.class, e);        }    }    // Process each annotation that corresponds to a listener.    for (Class<? extends Annotation> listener : LISTENERS) {        findAndParseListener(env, listener, 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);        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 {                // Has a superclass binding but we haven't built it yet. Re-enqueue for later.                entries.addLast(entry);            }        }    }    return bindingMap;}

方法代码很多,主要是对下面注解的处理:

这里写图片描述

处理完毕后会通过JavaFile javaFile = binding.brewJava(sdk, debuggable);生成Java文件。

接着看生成的文件比如我们的MainActivity_ViewBinding文件,路径在build/generated/source/apt/debug/packgename/下:

public class MainActivity extends AppCompatActivity {    @BindView(R.id.btn_test)    Button mButton;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);        mButton.setText("测试");    }    @OnClick(R.id.btn_test)    public void clickButton() {        Toast.makeText(this, "test", Toast.LENGTH_SHORT).show();    }}public class MainActivity_ViewBinding implements Unbinder {  private MainActivity target;  private View view2131427415;  @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.btn_test, "field 'mButton' and method 'clickButton'");    target.mButton = Utils.castView(view, R.id.btn_test, "field 'mButton'", Button.class);    view2131427415 = view;    view.setOnClickListener(new DebouncingOnClickListener() {      @Override      public void doClick(View p0) {        target.clickButton();      }    });  }  @Override  @CallSuper  public void unbind() {    MainActivity target = this.target;    if (target == null) throw new IllegalStateException("Bindings already cleared.");    this.target = null;    target.mButton = null;    view2131427415.setOnClickListener(null);    view2131427415 = null;  }}public abstract class DebouncingOnClickListener implements View.OnClickListener {  static boolean enabled = true;  private static final Runnable ENABLE_AGAIN = new Runnable() {    @Override public void run() {      enabled = true;    }  };  @Override public final void onClick(View v) {    if (enabled) {      enabled = false;      v.post(ENABLE_AGAIN);      doClick(v);    }  }  public abstract void doClick(View v);}

在上面通过ButterKnife的bind方法会通过两个参数的构造函数进行实例化,在构造函数中,可以看出最终还是会调用findViewById方法,并对view进行点击事件的监听,DebouncingOnClickListener是View.OnclickListener的子类,用于防止一定时间内对View的多次点击,在onClick方法中执行抽象方法doClick,在MainActivity_ViewBinding的构造函数中,可以看到为Button的点击时会调用MainActivity的clickButton方法,也就是在MainActivity中通过注解@OnClick定义的方法。

原创粉丝点击