Android 注解的使用 xUtils3和ButterKnife控件的注解注入对比

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/alcoholdi/article/details/60597715

Java注解的定义:

java注解(Annotation),是JDK1.5开始加入的源代码的一种特殊语法元信息。可以用于标注Java语言中的类、方法、变量、参数和包,然后在编译或运行时进行解析和使用,起到说明,配置的功能。注解的功能位于java.lang.annotation包中。

 

 

JDK里常见的有@Override、@Deprecated、@SuppressWarnings。

我刚开始对注解的认识,更多来自于以前做的Java Web开发,比如Spring的注解,@Controller、@ Service、@ Repository,Spring是利用这些注解达到标记的效果,从而实现IOC的功能,把本应该代码new出来的实体交给Spring容器来管理。

 

 

想要实现(定义)一个注解,就需要用到元注解了。比如Override,它的代码是这样的,上面两个就是元注解

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

 

元注解共有4种, @Retention、@Target、@Document、@Inherited四种。

 

@Document:表示带有这个注解的元素会被javadoc工具记录,不常用。

 

@Inherited:表示被注解的类,继承它的子类会自动继承此注解,一般常用。

 

@Target:用来确定注解的作用目标,包括以下几种

@Target(ElementType.TYPE)   //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR)  //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包

 

@Retention:定义注解的保留策略。分为如下几种

@Retention(RetentionPolicy.SOURCE)   //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS)     // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME)  // 注解会在class字节码文件中存在,在运行时可以通过反射获取到

 

 

----------------------------------------------------分割线----------------------------------------------------

 

下面来对比一下两个著名的框架,xUtils3和ButterKnife它们两个对于控件所使用的注解的不同。先上两个控件注解的源码,

这是xUtils3的

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {

    int value();

    /* parent view id */
    int parentId() default 0;
}

这是ButterKnife的,版本为8.5.1

@Retention(CLASS) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

相同点是@Target的值都是FIELD,表示这是一个类属性注解。

 

然后最大的不同点就是@Retention了,一个是@Retention(RetentionPolicy.RUNTIME),一个是@Retention(CLASS),代码省略了,实际上就是@Retention(RetentionPolicy.CLASS)。

 

 

xUtils3实现UI注解的原理

代码写法

它的写法是这样的,在BaseActivity里面有句注入代码

public class BaseActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        x.view().inject(this);
    }
}

然后在任一个子类Activity里这么写。意思是这个Activity使用R.layout.activity_main这个布局,然后对于mViewPager这个控件类的成员变量,注入id为R.id.container的控件。

@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {

    @ViewInject(R.id.container)
    private ViewPager mViewPager;

    @ViewInject(R.id.toolbar)
    private Toolbar toolbar;
......

 

 

 

 

 

代码实现
整个过程开始的地方在x.view().inject(this);

x相当于一个工具类,里面有一个内部类Ext,用来管理xUtils里4大模块的Manager,所以x是Manager中的Manager。

x.view()作用是使用懒汉模式获取(或创建)一个ViewInjectorImpl的单例实体。人如其名,其中ViewInjectorImpl继承了ViewInjector。

下面看看ViewInjectorImpl的inject方法的处理过程。

 

    @Override
    public void inject(Activity activity) {
        //获取Activity的ContentView的注解
        //获取Activity的Class类型
        Class<?> handlerType = activity.getClass();
        try {
            //根据类名,获取ContentView这个注解
            ContentView contentView = findContentView(handlerType);
            if (contentView != null) {
                //获取注解里value的值,也就是@ContentView(R.layout.activity_main)括号里这个int型资源ID
                int viewId = contentView.value();
                if (viewId > 0) {
                    //利用反射获取Activity的setContentView这个方法,通过Method.invoke最终完成setContentView(布局id)这句代码
                    Method setContentViewMethod = handlerType.getMethod("setContentView", int.class);
                    setContentViewMethod.invoke(activity, viewId);
                }
            }
        } catch (Throwable ex) {
            LogUtil.e(ex.getMessage(), ex);
        }


        //用来给控件成员注入的方法
        injectObject(activity, handlerType, new ViewFinder(activity));
    }

    private static ContentView findContentView(Class<?> thisCls) {
        //IGNORED是一个HashSet<Class<?>>,放置Object、Activity、Fragment以后supportV4包的Activity、Fragment
        //由于这是一个递归调用,会一直往父类去查询,所以当到了这几个类时候则结束递归。
        if (thisCls == null || IGNORED.contains(thisCls)) {
            return null;
        }
        //利用反射从Class实体中尝试获取ContentView这个注解,找不到就递归往父类去查找,找到就返回
        ContentView contentView = thisCls.getAnnotation(ContentView.class);
        if (contentView == null) {
            return findContentView(thisCls.getSuperclass());
        }
        return contentView;
    }

    @SuppressWarnings("ConstantConditions")
    //handler即是XXActivity类,handlerType是这个类的Class,这个ViewFinder里面是一个Activity或View,主要是对findViewById代码的封装
    private static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {

        //IGNORED过滤,同上
        if (handlerType == null || IGNORED.contains(handlerType)) {
            return;
        }


        // 从父类到子类递归
        injectObject(handler, handlerType.getSuperclass(), finder);


        // inject view 获取所有的类变量
        Field[] fields = handlerType.getDeclaredFields();
        if (fields != null && fields.length > 0) {
            for (Field field : fields) {

                Class<?> fieldType = field.getType();
                if (
                /* 不注入静态字段 */     Modifier.isStatic(field.getModifiers()) ||
                /* 不注入final字段 */    Modifier.isFinal(field.getModifiers()) ||
                /* 不注入基本类型字段 */  fieldType.isPrimitive() ||
                /* 不注入数组类型字段 */  fieldType.isArray()) {
                    continue;
                }

                //尝试获取变量ViewInject这个注解(可能为null)
                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                if (viewInject != null) {
                    try {
                        //ViewInject 可以在括号里填的两个值,一个是控件id,一个是控件的父控件id(不填默认值为0)
                        //这就是实现我们一般在Activity内写的findViewById方法。
                        View view = finder.findViewById(viewInject.value(), viewInject.parentId());
                        if (view != null) {
                            //这句话等于打开一个权限,从而可以使我们对private控件赋值。详情看下面setAccessible说明
                            field.setAccessible(true);
                            field.set(handler, view);
                        } else {
                            throw new RuntimeException("Invalid @ViewInject for "
                                    + handlerType.getSimpleName() + "." + field.getName());
                        }
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                }
            }
        } // end inject view


        // inject event 关于事件比如点击事件的注解,代码略......

    }


setAccessible说明,这里有篇文章有介绍AccessibleObject revisited: a study in immutability  里面有段话
The setAccessible method allows you to bypass the access control semantics of the Java language. By calling setAccessible on the Method object for a private method, you can call that method from outside the class it is defined in, using Method.invoke. By calling setAccessible on the Field object for a private field, you can read or write that field from any other class. As of Tiger, you can even modify a final field in this way.
大概意思是使用了setAccessible方法可以在类外面调用类的私有方法,读写类的私有属性,甚至可以修改一个final关键字修饰的变量。

 

 

所以小总结一下,xUtils里面的控件注解,就是在程序运行时,利用反射获取当前Activity类,反射获取并遍历所有类属性,挑出有@ViewInject注解的属性,获取里面的value值(即控件id),执行我们熟悉的findViewById代码得到View,最后把这个View用反射注入回到这个属性里。

 

ButterKnife实现UI注解的原理

关于编译时注解,有一篇博客讲解的很好。自定义注解之编译时注解(RetentionPolicy.CLASS)(一)

适合没接触过这个知识点的同学看。

 

 

这里拿ButterKnife最常用的BindView注解讲讲,写个简单demo

 

public class TestActivity extends AppCompatActivity {

    @BindView(R.id.tvContent)
    TextView tvContent;

    @BindView(R.id.btnOk)
    Button btnOk;

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

 

 

 

编译时注解需要继承抽象类AbstractProcessor,所以搜索框架源码找到了ButterKnifeProcessor.java

@AutoService(Processor.class)
public final class ButterKnifeProcessor extends AbstractProcessor {

  @Override
  public Set<String> getSupportedAnnotationTypes() {
    Set<String> types = new LinkedHashSet<>();
    for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
      types.add(annotation.getCanonicalName());
    }
    return types;
  }

  private Set<Class<? extends Annotation>> getSupportedAnnotations() {
    Set<Class<? extends Annotation>> 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);//一个常量List,包括onClick,onItemClick,onLongClick等所有Android的Listener

    return annotations;
  }
  
  @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);
      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) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();

    scanForRClasses(env);

    // Process each @BindArray element.
    // Process each @BindBitmap element.
    // Process each @BindBool element.
    // Process each @BindColor element.
    // Process each @BindDimen element.
    // Process each @BindDrawable element.
    // Process each @BindFloat element.
    // Process each @BindInt element.
    // Process each @BindString element.
    
    // 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.
    // 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;
  }
  
  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;
    }

    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value();

    BindingSet.Builder builder = builderMap.get(enclosingElement);
    QualifiedId qualifiedId = elementToQualifiedId(element, id);
    if (builder != null) {
      String existingBindingName = builder.findExistingBindingName(getId(qualifiedId));
      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(getId(qualifiedId), new FieldViewBinding(name, type, required));

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

 

 

 

 

 

·类注解@AutoService是google公司开发的注解处理器,能方便实现自定义注解,ButterKnife使用了这给力的功能。


getSupportedAnnotationTypes()方法返回值Set<String>,是确定这个Processor可以处理哪些注解的。可以看到下面的私有方法getSupportedAnnotations()里的class类型就是ButterKnife里面用到的注解。


process()在AbstractProcessor是唯一一个Abstract类型的方法,我们主要写的代码就是写这个方法体。方法很重要,分为以下几步

1.

第一行代码findAndParseTargets()就是扫描所有文件,找出类里面被ButterKnife里面注解的那些变量,可以看到有@BindBitmap、@BindInt、@BindDimen等一大堆。

里面有用到一个很重要的方法env.getElementsAnnotatedWith(BindView.class),意思是返回使用给定注释类型注释的元素。很拗口,在这里其实当前意思是返回用BindView注解的Field这个Element实体。

然后交由parseBindView方法处理,方法里有句代码int id = element.getAnnotation(BindView.class).value();是获取到写在BindView注解里面的一个资源int类型id。

之后就是用一个BindingSet来封装这个属性,资源id等重要信息。

2.

下一步看代码名字就知道了,用BindingSet的信息来生成一个JavaFile对象。人如其名,这个类主要是确定一个.java文件有哪些代码。这部分代码square公司写的很长很精妙,JavaFile这个类还不是属于ButterKnife项目的,是square公司的另一个项目javapoet,这个项目是专门用来生成.java源码文件的。

这里找出BindingSet的3个跟这个demo相关的方法。第一个方法,是生成一个属于Activity_ViewBinding的构造函数。第二个方法最重要,生成实现注解注入的构造函数。第三个方法,生成unbind函数。

 

  private MethodSpec createBindingConstructorForActivity() {
    MethodSpec.Builder builder = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC)
        .addParameter(targetTypeName, "target");
    if (constructorNeedsView()) {
      builder.addStatement("this(target, target.getWindow().getDecorView())");
    } else {
      builder.addStatement("this(target, target)");
    }
    return builder.build();
  }
  
  private MethodSpec createBindingConstructor(int sdk) {
    MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
        .addAnnotation(UI_THREAD)
        .addModifiers(PUBLIC);

    if (hasMethodBindings()) {
      constructor.addParameter(targetTypeName, "target", FINAL);
    } else {
      constructor.addParameter(targetTypeName, "target");
    }

    if (constructorNeedsView()) {
      constructor.addParameter(VIEW, "source");
    } else {
      constructor.addParameter(CONTEXT, "context");
    }

    if (hasUnqualifiedResourceBindings()) {
      // Aapt can change IDs out from underneath us, just suppress since all will work at runtime.
      constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
          .addMember("value", "$S", "ResourceType")
          .build());
    }

    if (hasOnTouchMethodBindings()) {
      constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
          .addMember("value", "$S", "ClickableViewAccessibility")
          .build());
    }

    if (parentBinding != null) {
      if (parentBinding.constructorNeedsView()) {
        constructor.addStatement("super(target, source)");
      } else if (constructorNeedsView()) {
        constructor.addStatement("super(target, source.getContext())");
      } else {
        constructor.addStatement("super(target, context)");
      }
      constructor.addCode("\n");
    }
    if (hasTargetField()) {
      constructor.addStatement("this.target = target");
      constructor.addCode("\n");
    }

    if (hasViewBindings()) {
      if (hasViewLocal()) {
        // Local variable in which all views will be temporarily stored.
        constructor.addStatement("$T view", VIEW);
      }
      for (ViewBinding binding : viewBindings) {
        addViewBinding(constructor, binding);
      }
      for (FieldCollectionViewBinding binding : collectionBindings) {
        constructor.addStatement("$L", binding.render());
      }

      if (!resourceBindings.isEmpty()) {
        constructor.addCode("\n");
      }
    }

    if (!resourceBindings.isEmpty()) {
      if (constructorNeedsView()) {
        constructor.addStatement("$T context = source.getContext()", CONTEXT);
      }
      if (hasResourceBindingsNeedingResource(sdk)) {
        constructor.addStatement("$T res = context.getResources()", RESOURCES);
      }
      for (ResourceBinding binding : resourceBindings) {
        constructor.addStatement("$L", binding.render(sdk));
      }
    }

    return constructor.build();
  }

  private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass) {
    MethodSpec.Builder result = MethodSpec.methodBuilder("unbind")
        .addAnnotation(Override.class)
        .addModifiers(PUBLIC);
    if (!isFinal && parentBinding == null) {
      result.addAnnotation(CALL_SUPER);
    }

    if (hasTargetField()) {
      if (hasFieldBindings()) {
        result.addStatement("$T target = this.target", targetTypeName);
      }
      result.addStatement("if (target == null) throw new $T($S)", IllegalStateException.class,
          "Bindings already cleared.");
      result.addStatement("$N = null", hasFieldBindings() ? "this.target" : "target");
      result.addCode("\n");
      for (ViewBinding binding : viewBindings) {
        if (binding.getFieldBinding() != null) {
          result.addStatement("target.$L = null", binding.getFieldBinding().getName());
        }
      }
      for (FieldCollectionViewBinding binding : collectionBindings) {
        result.addStatement("target.$L = null", binding.name);
      }
    }

    if (hasMethodBindings()) {
      result.addCode("\n");
      for (ViewBinding binding : viewBindings) {
        addFieldAndUnbindStatement(bindingClass, result, binding);
      }
    }

    if (parentBinding != null) {
      result.addCode("\n");
      result.addStatement("super.unbind()");
    }
    return result.build();
  }

3.然后调用javaFile.writeTo(filer);最终生成出来一个.java文件。

 

这段代码最终会生成什么,项目打包后用jadx反编译来看看。

 

import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.Utils;

public class TestActivity_ViewBinding implements Unbinder {
    private TestActivity target;

    @UiThread
    public TestActivity_ViewBinding(TestActivity target) {
        this(target, target.getWindow().getDecorView());
    }

    @UiThread
    public TestActivity_ViewBinding(TestActivity target, View source) {
        this.target = target;
        target.tvContent = (TextView) Utils.findRequiredViewAsType(source, R.id.tvContent, "field 'tvContent'", TextView.class);
        target.btnOk = (Button) Utils.findRequiredViewAsType(source, R.id.btnOk, "field 'btnOk'", Button.class);
    }

    @CallSuper
    public void unbind() {
        TestActivity target = this.target;
        if (target == null) {
            throw new IllegalStateException("Bindings already cleared.");
        }
        this.target = null;
        target.tvContent = null;
        target.btnOk = null;
    }
}

可以看到ButterKnife给我们的TestActivity生成了一个TestActivity_ViewBinding类,实现Unbinder接口。里面的第二个构造函数就是我们BindView注解会生成的业务代码。里面两句Utils.findRequiredViewAsType赋值给控件的代码,我想你已经猜到啥意思了。不深追了。

 

 

那么这个TestActivity_ViewBinding是怎么用,什么时候用的?

回到我们TestActivity的onCreate()方法,我们看到了这句代码ButterKnife.bind(this); 所以我们进入ButterKnife.java看看

 

  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return createBinding(target, sourceView);
  }
  
  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);
    }
  }
  
  @Nullable @CheckResult @UiThread
  private 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 = Class.forName(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;
  }

 

bind重载了很多方法,针对Activity、Dialog、Fragment、View的。都是调用了createBinding()方法。

createBinding()方法中通过findBindingConstructorForClass方法传入TestActivity.class获得一个构造函数。

什么构造函数呢?在findBindingConstructorForClass可以看到通过Class.forName(clsName + "_ViewBinding")字符串拼接的方法,最终能获得我们的TestActivity_ViewBinding类。

然后调用bindingClass.getConstructor(cls, View.class);获取参数列表第一个是当前类,第二个是View类型的构造函数,也就是上面所说的核心业务构造函数啦。

最后在createBinding方法中,调用constructor.newInstance(target, source)调用起这个构造函数。

截止到这就完成我们所有findViewById代码的功能了。

 

小总结一下,ButterKnife里面的控件注解,会在程序编译(生成apk)时,生成XXX_ViewBinding类,里面的(构造)方法会有一些findViewById的代码。然后也会用反射调用到这个类的构造函数。


 


两个注入框架的对比结论

总结就是xUtils3是通过运行时注解利用反射,破解私有属性等手段实现控件的注入,ButterKnife是通过编译时注解,生成一个附属类代码,然后在这些代码插入到我们代码中执行。所以程序在运行时,ButterKnife会比xUtils更快。

 

 

//TODO

做个对比demo测试一下两个库的速度。

 

 

 

参考文章:

http://blog.csdn.net/yixiaogang109/article/details/7328466

http://www.trinea.cn/android/java-annotation-android-open-source-analysis/
 

展开阅读全文

没有更多推荐了,返回首页