Android杂谈(21)+Java随笔(4):注解(下)分析ButterKnife

转载请注意:http://blog.youkuaiyun.com/wjzj000/article/details/54177914
本菜开源的一个自己写的Demo,希望能给Androider们有所帮助,水平有限,见谅见谅…
https://github.com/zhiaixinyang/PersonalCollect (拆解GitHub上的优秀框架于一体,全部拆离不含任何额外的库导入)
https://github.com/zhiaixinyang/MyFirstApp(Retrofit+RxJava+MVP)


注解(上):http://blog.youkuaiyun.com/wjzj000/article/details/53227352


安卓开发或多或少都会用到注解,无论是内置的注解还是其他的框架。我们都能感受到注解的作用,这里梳理一下ButterKinfe框架,也借此机会记录一下注解的用法。
在分析它的开始我们先简单写一个类似的框架,让我们更方便的去理解。

开始搞事情

  • 直接贴代码,因为 思路比价清晰,解析过程主要是最基本的反射。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyBindView {
    int value() default 0;
}
public class MyBufferKnife {
    public static void bind(Activity activity){
        getAnnotationInfos(activity);
    }
    private static void getAnnotationInfos(Activity activity) {
        Class clazz = activity.getClass();

        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            MyBindView myBindView = field.getAnnotation(MyBindView.class);
            if (myBindView != null) {
                int id = myBindView.value();
                try {
                    field.setAccessible(true);
                    field.set(activity, activity.findViewById(id));
                } catch (IllegalAccessException e) {
                }
            }
        }
    }
}
public class MyButterKnifeActivity extends Activity{
    @MyBindView(R.id.text) TextView text;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_butterknife);
        MyBufferKnife.bind(this);
        text.setText("我是MyButterKnife!");
    }
}

  • 接下来让我们走进ButterKnife

Go

基本切入

  • 最开始我们需要在Gradle中添加:
compile 'com.jakewharton:butterknife:8.4.0'
apt 'com.jakewharton:butterknife-compiler:8.4.0'
  • 最基本的应用我们会这么来写,首先是声明控件:
@BindView(R.id.rlv_main) RecyclerView rlvMain;

//让我们简单看一看这个注解
/**
 * Bind a field to the view for the specified ID. The view will automatically be cast to the field type.
 * 翻译:将字段绑定到指定ID的View,视图将自动转换为字段类型。
 * <pre><code>
 * {@literal @}BindView(R.id.title) TextView title;
 * </code></pre>
 */
@Retention(CLASS) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. 
   *  翻译:View将绑定字段的ID。(@IdRes注解时安卓内置的)
   */
  @IdRes int value();
}
/**
 * Denotes that an integer parameter, field or method return value is expected
 * to be an id resource reference (e.g. {@code android.R.id.copy}).
 * 翻译:表示整数参数,字段或方法返回值应为id资源引用(例如{@code android.R.id.copy})
 */
@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
public @interface IdRes {
}
  • 其次便是在特定的地方就行绑定:
ButterKnife.bind(this);

我们都知道,注解的运转靠的是注解解释器。因此,接下来让我们进入ButterKnife,来看一看它内部的运转!
Ok,马上开始。让我们进入框架的入口:

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

这里作者用到了俩个注解,一个不能为空(字段或方法返回值永远不能为空),一个是表示运行在Ui线程。
返回一个方法createBinding(),这里传进了一个我们传进来的Activity以及通过Activity获取的根部View。

进入ButterKnife

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());
    //findBindingConstructorForClass()此方法的实现在下边展开
    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);
        }
  }

很简单,通过反射拿到Activity的Class,然后传到findBindingConstructorForClass()方法。
看的代码的时候让我们自动屏蔽一些判空啊,抛异常之类….它对我们的梳理代码没有关系…

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    /**
     * static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
     * 这里通过通过传递的Class从Map中拿到Constructor
     * Unbinder:作者的一个接口...下文有展开。
     */
    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 对象。
        Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
        /**
         * public Constructor<T> getConstructor(Class<?>... parameterTypes)
         * 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
         * parameterTypes 参数是 Class 对象的一个数组,这些 Class 对象按声明顺序标识构造方法的形参类型。 
         * 如果此 Class 对象表示非静态上下文中声明的内部类,则形参类型作为第一个参数包括显示封闭的实例。 
         */
        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);
    }
    //获取Constructor之后,加入到Map之中
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
}
/** An unbinder contract that will unbind views when called. */
// 通过它的命名我们可以大概知道含义,Unbinder:解绑定。
public interface Unbinder {
  @UiThread void unbind();
  Unbinder EMPTY = new Unbinder() {
    @Override public void unbind() { }
  };
}

!!

到此ButterKnife这个类基本上就没有没有了处理业务的其他代码。
WTF?没了?没了…
让我们拉开ButterKnife的源码结构:
这里写图片描述

进入Utils

接下来我们看一看这里的方法。

public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
      Class<T> 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.");
}
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
          //将一个对象强制转换成此 Class 对象所表示的类或接口。
          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);
    }
}
public static <T> T findOptionalViewAsType(View source, @IdRes int id, String who,
      Class<T> cls) {
      //在这我们通过传递进来的View进行findViewById()
      View view = source.findViewById(id);
      return castView(view, id, who, cls);
}

在Utils之中我们可以看到我们想看到的东西也就是findViewById(),但是它任何被调用,我们似乎没有很明确的看到。
其实这些都蕴含在我们在Gradle的引用之中:
apt ‘com.jakewharton:butterknife-compiler:8.4.0’
apt就是注解解释工具。因此我们可以推断出相关的调用就在apt之中。


APT

APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。
Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。


最后希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:
https://github.com/zhiaixinyang/PersonalCollect
https://github.com/zhiaixinyang/MyFirstApp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值