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

来源:互联网 发布:jetbrains软件 编辑:程序博客网 时间:2024/05/20 09:21

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


注解(上):http://blog.csdn.net/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 @UiThreadpublic 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 @UiThreadprivate 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文件。

  • 关于这方面的内容各位看官可以看一下:
  • http://www.open-open.com/lib/view/open1462925582182.html

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

1 0
原创粉丝点击