Butterknife用法
我相信学过android开发应该基本上都用过Butterknife吧,就算没用过也听说过吧?毕竟是大名鼎鼎的Jake Wharton出品的东西,如果没用过,就分享下面这篇《java基础之注解annotation》里面虽然是讲的Annotation,但是例子就是用注解加反射实现的低级的Butterknife。哈哈!用法里面大概也说了下。
前言
从 jdk5开始,Java增加了对元数据的支持,也就是Annotation,Annotation其实就是对代码的一种特殊标记,这些标记可以在编译,类加载和运行时被读取,并执行相应的处理。当然刚刚说了,Annotation只是一种标记,所以要是在代码里面不用这些标记也是能完成相应的工作的,只是有时候用注解能简化很多代码,看起来非常的简洁。
基本的Annotation
- @Override——限定重写父类方法
- @Deprecated——标示已过时
- @SuppressWarning——抑制编译器警告
- @SafeVarargs——这货与Java7里面的堆污染有关,具体想了解的,传送到这里
JDK的元Annotation
JDK除了提供上述的几种基本的Annotation外,还提供了几种Annotation,用于修饰其他的Annotation定义
- @Retention 这个是决定你Annotation存活的时间的,它包含一个RetationPolicy的value成员变量,用于指定它所修饰的Annotation保留时间,一般有:
- Retationpolicy.CLASS:编译器将把Annotation记录在Class文件中,不过当java程序执行的时候,JVM将抛弃它。
- Retationpolicy.SOURCE : Annotation只保留在原代码中,当编译器编译的时候就会抛弃它。
- Retationpolicy.RUNTIME : 在Retationpolicy.CLASS的基础上,JVM执行的时候也不会抛弃它,所以我们一般在程序中可以通过反射来获得这个注解,然后进行处理。
- @Target 这个注解一般用来指定被修饰的Annotation修饰哪些元素,这个注解也包含一个value变量:
- ElementType.ANNOTATION_TYPE : 指定该Annotation只能修饰Annotation。
- ElementType.CONSTRUCTOR: 指定只能修饰构造器。
- ElementType.FIELD: 指定只能成员变量。
- ElementType.LOCAL_VARIABLE: 指定只能修饰局部变量。
- ElementType.METHOD: 指定只能修饰方法。
- ElementType.PACKAGE: 指定只能修饰包定义。
- ElementType.PARAMETER: 指定只能修饰参数。
- ElementType.TYPE: 指定可以修饰类,接口,枚举定义。
- @Document 这个注解修饰的Annotation类可以被javadoc工具提取成文档
- @Inherited 被他修饰的Annotation具有继承性
自定义Annotation
上面讲了一些jdk自带的Annotation,那么我们现在就可以用这些jdk自带的Annotation来实现一些我们想要的功能。由于最近在看butterknife的源码,那么我们就一步一步地模仿butterknife的实现吧。
首先先讲一下的用法吧:
@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.text_view)
private TextView textView;
@OnClick(R.id.text_view)
private void onClick(View view){
textView.setText("我是click后的textview");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewInjectUtils.inject(this);
textView.setText("我是click前的textview");
}
}
上面是这篇文章最后的实现,自从用了注解后,妈妈再也不用担心我一遍一遍地写findViewById和setOnClickListener了。
编码
首先我们要先定义我们要用的接口,哦不!是注解。注意和接口不一样哦!这里我们先实现@ContentView
的功能,再来实现@ViewInject
和@OnClick
package com.qhung.annotation.ioc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by qhung on 2016/5/3.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
啊,这里的@Target和@Retention大家应该都清楚是什么意思了哈,定义注解的方式就是@interface
和接口的定义方式就少一个@哦,不要搞混了。里面有一个变量value,就是我们使用的时候@ContentView(R.layout.activity_main)
指定的R.layout.activity_main
布局文件,旨在自动注入布局文件。因为这里只有一个变量value,所以不用写成name=value的形式。
然后大家还记得我们在Activity里面调用的ViewInjectUtils.inject(this);
?哈哈!其实我们处理注解的逻辑全在这个里面,那么我们就看看这个里面又是一番什么天地:
package com.qhung.annotation.ioc.annotation;
import android.app.Activity;
import android.view.View;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
*
* Created by qhung on 2016/5/3.
*/
public class ViewInjectUtils {
public static void inject(Activity activity) {
injectContentView(activity);
}
private static void injectContentView(Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView != null) {
//如果这个activity上面存在这个注解的话,就取出这个注解对应的value值,其实就是前面说的布局文件。
int layoutId = contentView.value();
try {
Method setViewMethod = clazz.getMethod("setContentView", int.class);
setViewMethod.invoke(activity, layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
原来ViewInjectUtils.inject(this)
里面调用了injectContentView(activity)
,在injectContentView(activity)
里面,我们拿到了Activity的Class,然后在第23行,我们拿到了这个class的ContentView注解,然后再通过反射调用setContentView方法,完成注入。其实这里是在运行的时候完成的,所以我们在定义注解的时候,设置为Retention为RUNTIME。
好了,这个功能到这里就完了。下面继续完成第二个功能:ViewInject
同样先贴上ViewInject类:
package com.qhung.annotation.ioc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by qhung on 2016/5/3.
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewInject {
int value();
}
其实这个定义和上面ContentView的定义一样,然后我们再看看我们是怎么处理的:
public static void inject(Activity activity) {
injectContentView(activity);
injectView(activity);
}
private static void injectView(Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
//获得activity的所有成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//获得每个成员变量上面的ViewInject注解,没有的话,就会返回null
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
int viewId = viewInject.value();
View view = activity.findViewById(viewId);
try {
field.setAccessible(true);
field.set(activity, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
获得所有属性,然后遍历带有ViewInject注解的属性,然后拿到ViewInject注解的View的id,然后通过activity.findViewById(viewId);
获得这个View。然后设置给field。
最后一个功能:EventInject
这个功能稍微麻烦一点,因为我们平时设置的点击时间是用setiOnClickListener();然后View.OnClickListener是一个接口,不能用反射来获得他的实例,那么怎么办呢?
其实我们这里可以巧妙地用动态代理来完成,当view被点击的时候,我们通过动态代理来调用onclick就行。
下面是代码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
int[] value();
}
public class ViewInjectUtils {
public static void inject(Activity activity) {
injectContentView(activity);
injectView(activity);
injectEvent(activity);
}
private static void injectEvent(final Activity activity) {
Class<? extends Activity> clazz = activity.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (final Method method2 : methods) {
OnClick click = method2.getAnnotation(OnClick.class);
if (click != null) {
int[] viewId = click.value();
method2.setAccessible(true);
Object listener = Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(),
new Class[]{View.OnClickListener.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method2.invoke(activity, args);
}
});
try {
for (int id : viewId) {
View v = activity.findViewById(id);
Method setClickListener = v.getClass().getMethod("setOnClickListener", View.OnClickListener.class);
setClickListener.invoke(v, listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
listener是一个代理对象,然后我们调用setOnClickListener的时候,把这个代理对象传进去。当发生点击的时候,就会invoke方法,这时我们就可以调用带有onClick注解的method方法了。
下面看一下运行结果:
现在我们就完成了类似butterknife的功能了,不过butterknife最新版本不是通过反射来完成的,因为反射会有性能问题,虽然现在对性能影响不大,但是作为程序员,能优化的就要尽量优化,不能只停留在能用的基础上面。
Butterknife原理
讲到butterknife的原理。这里不得不提一下一般这种注入框架都是运行时注解,即声明注解的生命周期为RUNTIME,然后在运行的时候通过反射完成注入,这种方式虽然简单,但是这种方式多多少少会有性能的损耗。那么有没有一种方法能解决这种性能的损耗呢? 没错,答案肯定是有的,那就是Butterknife用的APT(Annotation Processing Tool)编译时解析技术。
APT大概就是你声明的注解的生命周期为CLASS,然后继承AbstractProcessor类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor的process方法,对注解进行处理,那么我们就可以在处理的时候,动态生成绑定事件或者控件的java代码,然后在运行的时候,直接调用bind方法完成绑定。
其实这种方式的好处是我们不用再一遍一遍地写findViewById和onClick了,这个框架在编译的时候帮我们自动生成了这些代码,然后在运行的时候调用就行了。
源码解析
上面讲了那么多,其实都不如直接解析源码来得直接,下面我们就一步一步来探究大神怎样实现Butterknife的吧。
拿到源码的第一步是从我们调用的地方来突破,那我们就来看看程序里面是怎样调用它的呢?
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.setDebug(true);
ButterKnife.bind(this);
// Contrived code to use the bound fields.
title.setText("Butter Knife");
subtitle.setText("Field and method binding for Android views.");
footer.setText("by Daxia");
hello.setText("Say Hello");
adapter = new SimpleAdapter(this);
listOfThings.setAdapter(adapter);
}
上面是github上给的例子,我们直接就从 ButterKnife.bind(this)
入手吧,点进来看看:
public static Unbinder bind(@NonNull Activity target) {
return bind(target, target, Finder.ACTIVITY);
}
咦?我再点:
static Unbinder bind(@NonNull Object target, @NonNull Object source, @NonNull Finder finder) {
Class<?> targetClass = target.getClass();
try {
ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
return viewBinder.bind(finder, target, source);
} catch (Exception e) {
throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
}
}
好吧,bind方法主要就是拿到我们绑定的Activity的Class,然后找到这个Class的ViewBinder,最后调用ViewBinder的bind()
方法,那么问题来了,ViewBinder是个什么鬼???我们打开
findViewBinderForClass()
方法。
@NonNull
private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
throws IllegalAccessException, InstantiationException {
ViewBinder<Object> viewBinder = BINDERS.get(cls);
if (viewBinder != null) {
return viewBinder;
}
String clsName = cls.getName();
try {
Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder");
viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
} catch (ClassNotFoundException e) {
viewBinder = findViewBinderForClass(cls.getSuperclass());
}
BINDERS.put(cls, viewBinder);
return viewBinder;
}
这里我去掉了一些Log信息,保留了关键代码,上面的BINDERS是一个保存了Class为key,Class$$ViewBinder
为Value的一个LinkedHashMap,主要是做一下缓存,提高下次再来bind的性能。
在第10行的时候,clsName 是我们传入要绑定的Activity类名,这里相当于拿到了Activity$$ViewBinder
这个东西,这个类又是什么玩意儿?其实从类名可以看出来,相当于Activity的一个内部类,这时候我们就要问了,我们在用的时候没有声明这个类啊???从哪里来的? 不要方,其实它就是我们在之前讲原理的时候说到的AbstractProcessor在编译的时候生成的一个类,我们后面再来看它,现在我们继续往下面分析。在第11行就用反射反射了一个viewBinder
实例出来。
刚刚说了,这个方法里面用linkhashMap做了下缓存,所以在15行的时候,就把刚刚反射的viewBinder作为value,Class作为key加入这个LinkedHashMap,下次再bind这个类的时候,就直接在第4行的时候取出来用,提升性能。
现在返回刚刚的bind方法,我们拿到了这个Activity的viewBinder,然后调用它的bind方法。咦?这就完了???我们再点进viewBinder的bind方法看看。
public interface ViewBinder<T> {
Unbinder bind(Finder finder, T target, Object source);
}
什么,接口???什么鬼?刚刚不是new了一个viewBinder出来么?然后这里就调用了这个viewBinder的bind方法, 不行,我要看一下bind到底是什么鬼!上面说了,Butterknife用了APT技术,那么这里的viewBinder应该就是编译的时候生成的,那么我们就反编译下apk。看看到底生成了什么代码:
下面我们就先用一个简单的绑定TextView的例子,然后反编译出来看看:
public class MainActivity extends AppCompatActivity {
@Bind(R.id.text_view)
TextView textView;
@OnClick(R.id.text_view)
void onClick(View view) {
textView.setText("我被click了");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
textView.setText("我还没有被click");
}
}
源代码就这行几行,然后反编译看看:
源代码就多了一个类,MainActivity$$ViewBinder
,打开看看:
public class MainActivity$$ViewBinder<T extends MainActivity>
implements ButterKnife.ViewBinder<T>
{
public void bind(ButterKnife.Finder paramFinder, final T paramT, Object paramObject)
{
View localView = (View)paramFinder.findRequiredView(paramObject, 2131492944, "field ‘textView‘ and method ‘onClick‘");
paramT.textView = ((TextView)paramFinder.castView(localView, 2131492944, "field ‘textView‘"));
localView.setOnClickListener(new DebouncingOnClickListener()
{
public void doClick(View paramAnonymousView)
{
paramT.onClick(paramAnonymousView);
}
});
}
public void unbind(T paramT)
{
paramT.textView = null;
}
}
还记得刚刚说的,反射了一个Class$$ViewBinder
么?看这里的类名。现在应该懂了吧?它刚好也是实现了ButterKnife.ViewBinder<T>
接口,我们说了,在bind方法中,最后调用了ViewBinder的bind方法,先说下几个参数paramFinder其实就是一个Finder,因为我们可以在Activity中使用butterknife,也可以在Fragment和Adapter等中使用butterknife,那么在不同的地方使用butterknife,这个Finder也就不同。在Activity中,其实源码
就是这样子的:
ACTIVITY {
@Override protected View findView(Object source, int id) {
return ((Activity) source).findViewById(id);
}
@Override public Context getContext(Object source) {
return (Activity) source;
}
}
有没有很熟悉???其实还是用的findViewById
,那么在Dialog和Fragment中,根据不同的地方,实现的方式不同。
这里的paramT和paramObject都是我们要绑定的Activity类,通过代码可以跟踪到。
返回上面的ViewBinder代码,首先调用了Finder的findRequiredView方法,其实这个方法最后经过处理就是调用了findView方法,拿到相应的view,然后再赋值给paramT.textView,刚说了paramT就是那个要绑定的Activity,现在懂了吧?这里通过 paramT.textView 这样的调用方式,说明了Activity中不能把TextView设置为private,不然会报错,其实这里可以用反射来拿到textView的,这里大概也是为了性能着想吧。最后setOnClickListener,DebouncingOnClickListener
这个Listener其实也是实现了View.OnClickListener
方法,然后在OnClick里面调用了doClick
方法。流程大概跟踪了一遍。现在还留下最后一块了:
Butterknife到底是怎样在编译的时候生成代码的?
我们来看一下它的ButterKnifeProcessor
类:
Init方法:
@Override public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
}
ProcessingEnviroment参数提供很多有用的工具类Elements, Types和Filer。Types是用来处理TypeMirror的工具类,Filer用来创建生成辅助文件。至于ElementUtils嘛,其实ButterKnifeProcessor在运行的时候,会扫描所有的Java源文件,然后每一个Java源文件的每一个部分都是一个Element,比如一个包、类或者方法。
@Override public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindArray.class.getCanonicalName());
types.add(BindBitmap.class.getCanonicalName());
types.add(BindBool.class.getCanonicalName());
types.add(BindColor.class.getCanonicalName());
types.add(BindDimen.class.getCanonicalName());
types.add(BindDrawable.class.getCanonicalName());
types.add(BindInt.class.getCanonicalName());
types.add(BindString.class.getCanonicalName());
types.add(BindView.class.getCanonicalName());
types.add(BindViews.class.getCanonicalName());
for (Class<? extends Annotation> listener : LISTENERS) {
types.add(listener.getCanonicalName());
}
return types;
}
getSupportedAnnotationTypes()方法主要是指定ButterknifeProcessor是注册给哪些注解的。我们可以看到,在源代码里面,作者一个一个地把Class文件加到那个LinkedHashSet里面,然后再把LISTENERS也全部加进去。
其实整个类最重要的是process方法:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
try {
bindingClass.brewJava().writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
e.getMessage());
}
}
return true;
}
这个方法的作用主要是扫描、评估和处理我们程序中的注解,然后生成Java文件。也就是前面说的ViewBinder。首先一进这个函数就调用了findAndParseTargets
方法,我们就去看看findAndParseTargets
方法到底做了什么:
private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<>();
Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
// Process each @BindView element.
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
if (!SuperficialValidation.validateElement(element)) continue;
try {
parseBindView(element, targetClassMap, erasedTargetNames);
} catch (Exception e) {
logParsingError(element, BindView.class, e);
}
}
Observable.from(topLevelClasses)
.flatMap(new Func1<BindingClass, Observable<?>>() {
@Override public Observable<?> call(BindingClass topLevelClass) {
if (topLevelClass.hasViewBindings()) {
// It has an unbinder class and it will also be the highest unbinder class for all
// descendants.
topLevelClass.setHighestUnbinderClassName(topLevelClass.getUnbinderClassName());
} else {
// No unbinder class, so null it out so we know we can just return the NOP unbinder.
topLevelClass.setUnbinderClassName(null);
}
// Recursively set up parent unbinding relationships on all its descendants.
return ButterKnifeProcessor.this.setParentUnbindingRelationships(
topLevelClass.getDescendants());
}
})
.toCompletable()
.await();
return targetClassMap;
}
这里代码炒鸡多,我就不全部贴出来了,只贴出来一部分,这个方法最后还用了rxjava的样子。这个方法的主要的流程如下:
- 扫描所有具有注解的类,然后根据这些类的信息生成BindingClass,最后生成以TypeElement为键,BindingClass为值的键值对。
- 循环遍历这个键值对,根据TypeElement和BindingClass里面的信息生成对应的java类。例如AnnotationActivity生成的类即为
Cliass$$ViewBinder
类。
因为我们之前用的例子是绑定的一个View,所以我们就只贴了解析View的代码。好吧,这里遍历了所有带有@BindView
的Element,然后对每一个Element进行解析,也就进入了parseBindView
这个方法中:
private void parseBindView(Element element, Map<TypeElement, BindingClass> targetClassMap,
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();
}
if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) {
error(element, "@%s fields must extend from View or be an interface. (%s.%s)",
BindView.class.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName());
hasError = true;
}
if (hasError) {
return;
}
// Assemble information on the field.
int id = element.getAnnotation(BindView.class).value();
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass != null) {
ViewBindings viewBindings = bindingClass.getViewBinding(id);
if (viewBindings != null) {
Iterator<FieldViewBinding> iterator = viewBindings.getFieldBindings().iterator();
if (iterator.hasNext()) {
FieldViewBinding existingBinding = iterator.next();
error(element, "Attempt to use @%s for an already bound ID %d on ‘%s‘. (%s.%s)",
BindView.class.getSimpleName(), id, existingBinding.getName(),
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
}
} else {
bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
}
String name = element.getSimpleName().toString();
TypeName type = TypeName.get(elementType);
boolean required = isFieldRequired(element);
FieldViewBinding binding = new FieldViewBinding(name, type, required);
bindingClass.addField(id, binding);
// Add the type-erased version to the valid binding targets set.
erasedTargetNames.add(enclosingElement);
}
然后这里从一进入这个方法到
int id = element.getAnnotation(BindView.class).value();
都是在拿到注解信息,然后验证注解的target的类型是否继承自view,然后上面这一行代码获得我们要绑定的View的id,再从targetClassMap里面取出BindingClass(这个BindingClass是管理了所有关于这个注解的一些信息还有实例本身的信息,其实最后是通过BindingClass来生成java代码的),如果targetClassMap里面不存在的话,就在
bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
这里生成一个,我们进去看一下getOrCreateTargetClass
:
private BindingClass getOrCreateTargetClass(Map<TypeElement, BindingClass> targetClassMap,
TypeElement enclosingElement) {
BindingClass bindingClass = targetClassMap.get(enclosingElement);
if (bindingClass == null) {
String targetType = enclosingElement.getQualifiedName().toString();
String classPackage = getPackageName(enclosingElement);
boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL);
String className = getClassName(enclosingElement, classPackage) + BINDING_CLASS_SUFFIX;
String classFqcn = getFqcn(enclosingElement) + BINDING_CLASS_SUFFIX;
bindingClass = new BindingClass(classPackage, className, isFinal, targetType, classFqcn);
targetClassMap.put(enclosingElement, bindingClass);
}
return bindingClass;
}
这里面其实很简单,就是获取一些这个注解所修饰的变量的一些信息,比如类名呀,包名呀,然后className
这里就赋值成Class$$ViewHolder
了,因为:
private static final String BINDING_CLASS_SUFFIX = "$$ViewBinder";
然后把这个解析后的bindingClass加入到targetClassMap里面。
返回刚刚的parseBindView
中,根据view的信息生成一个FieldViewBinding,最后添加到上边生成的BindingClass实例中。这里基本完成了解析工作。最后回到findAndParseTargets
中:
Observable.from(topLevelClasses)
.flatMap(new Func1<BindingClass, Observable<?>>() {
@Override public Observable<?> call(BindingClass topLevelClass) {
if (topLevelClass.hasViewBindings()) {
topLevelClass.setHighestUnbinderClassName(topLevelClass.getUnbinderClassName());
} else {
topLevelClass.setUnbinderClassName(null);
}
return ButterKnifeProcessor.this.setParentUnbindingRelationships(
topLevelClass.getDescendants());
}
})
.toCompletable()
.await();
这里用到了rxjava,其实这里主要的工作是建立上面的绑定的所有的实例的解绑的关系,因为我们绑定了,最后在代码中还是会解绑的。这里预先处理好了这些关系。因为这里要递归地完成解绑,所以用了flatmap,flatmap把每一个创建出来的 Observable 发送的事件,都集中到同一个 Observable 中,然后这个 Observable 负责将这些事件统一交给 Subscriber 。
然而这部分涉及到很多rxjava的东西,有兴趣的童鞋去看看大神的写给android开发者的RxJava 详解这篇文章,然后再来看这里就很轻松了。
回到我们的process中, 现在解析完了annotation,该生成java文件了,我再把代码贴一下:
@Override public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
TypeElement typeElement = entry.getKey();
BindingClass bindingClass = entry.getValue();
try {
bindingClass.brewJava().writeTo(filer);
} catch (IOException e) {
error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
e.getMessage());
}
}
return true;
}
遍历刚刚得到的targetClassMap ,然后再一个一个地通过
bindingClass.brewJava().writeTo(filer);
来生成java文件。然而生成的java文件也是根据上面的信息来用字符串拼接起来的,然而这个工作在brewJava()中完成了:
JavaFile brewJava() {
TypeSpec.Builder result = TypeSpec.classBuilder(className)
.addModifiers(PUBLIC)
.addTypeVariable(TypeVariableName.get("T", ClassName.bestGuess(targetClass)));
if (isFinal) {
result.addModifiers(Modifier.FINAL);
}
if (hasParentBinding()) {
result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(parentBinding.classFqcn),
TypeVariableName.get("T")));
} else {
result.addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, TypeVariableName.get("T")));
}
result.addMethod(createBindMethod());
if (hasUnbinder() && hasViewBindings()) {
// Create unbinding class.
result.addType(createUnbinderClass());
if (!isFinal) {
// Now we need to provide child classes to access and override unbinder implementations.
createUnbinderCreateUnbinderMethod(result);
}
}
return JavaFile.builder(classPackage, result.build())
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build();
}
最后通过writeTo(Filer filer)生成java源文件。