转载请注意: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文件。
- 关于这方面的内容各位看官可以看一下:
- http://www.open-open.com/lib/view/open1462925582182.html
最后希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:
https://github.com/zhiaixinyang/PersonalCollect
https://github.com/zhiaixinyang/MyFirstApp