配色: 字号:
Butterknife源码
2016-12-06 | 阅:  转:  |  分享 
  
Butterknife源码

Butterknife用法

我相信学过Android开发应该基本上都用过Butterknife吧,就算没用过也听说过吧?毕竟是大名鼎鼎的JakeWharton出品的东西,如果没用过,就分享下面这篇《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来实现一些我们想要的功能。由于最近在看butterknife的源码,那么我们就一步一步地模仿butterknife的实现吧。

首先先讲一下的用法吧:

@ContentView(R.layout.activity_main)

publicclassMainActivityextendsAppCompatActivity{



@ViewInject(R.id.text_view)

privateTextViewtextView;



@OnClick(R.id.text_view)

privatevoidonClick(Viewview){

textView.setText("我是click后的textview");

}



@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

ViewInjectUtils.inject(this);

textView.setText("我是click前的textview");

}

}

编码

首先我们要先定义我们要用的接口,哦不!是注解。注意和接口不一样哦!这里我们先实现@ContentView的功能,再来实现@ViewInject和@OnClick

packagecom.qhung.annotation.ioc.annotation;



importjava.lang.annotation.ElementType;

importjava.lang.annotation.Retention;

importjava.lang.annotation.RetentionPolicy;

importjava.lang.annotation.Target;



/

Createdbyqhungon2016/5/3.

/

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public@interfaceContentView{

intvalue();

}

啊,这里的@Target和@Retention大家应该都清楚是什么意思了哈,定义注解的方式就是@interface和接口的定义方式就少一个@哦,不要搞混了。里面有一个变量value,就是我们使用的时候@ContentView(R.layout.activity_main)指定的R.layout.activity_main布局文件,旨在自动注入布局文件。因为这里只有一个变量value,所以不用写成name=value的形式。

然后大家还记得我们在Activity里面调用的ViewInjectUtils.inject(this);?哈哈!其实我们处理注解的逻辑全在这个里面,那么我们就看看这个里面又是一番什么天地:

packagecom.qhung.annotation.ioc.annotation;



importandroid.app.Activity;

importandroid.view.View;



importjava.lang.reflect.Field;

importjava.lang.reflect.InvocationHandler;

importjava.lang.reflect.Method;

importjava.lang.reflect.Proxy;



/



Createdbyqhungon2016/5/3.

/

publicclassViewInjectUtils{

publicstaticvoidinject(Activityactivity){

injectContentView(activity);

}



privatestaticvoidinjectContentView(Activityactivity){



Classclazz=activity.getClass();

ContentViewcontentView=clazz.getAnnotation(ContentView.class);

if(contentView!=null){

//如果这个activity上面存在这个注解的话,就取出这个注解对应的value值,其实就是前面说的布局文件。

intlayoutId=contentView.value();

try{

MethodsetViewMethod=clazz.getMethod("setContentView",int.class);

setViewMethod.invoke(activity,layoutId);

}catch(Exceptione){

e.printStackTrace();

}

}

}

}

原来ViewInjectUtils.inject(this)里面调用了injectContentView(activity),在injectContentView(activity)里面,我们拿到了Activity的Class,然后在第23行,我们拿到了这个class的ContentView注解,然后再通过反射调用setContentView方法,完成注入。其实这里是在运行的时候完成的,所以我们在定义注解的时候,设置为Retention为RUNTIME。

好了,这个功能到这里就完了。下面继续完成第二个功能:ViewInject

同样先贴上ViewInject类:

packagecom.qhung.annotation.ioc.annotation;



importjava.lang.annotation.ElementType;

importjava.lang.annotation.Retention;

importjava.lang.annotation.RetentionPolicy;

importjava.lang.annotation.Target;



/

Createdbyqhungon2016/5/3.

/

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public@interfaceViewInject{

intvalue();

}

其实这个定义和上面ContentView的定义一样,然后我们再看看我们是怎么处理的:

publicstaticvoidinject(Activityactivity){

injectContentView(activity);

injectView(activity);

}

privatestaticvoidinjectView(Activityactivity){

Classclazz=activity.getClass();

//获得activity的所有成员变量

Field[]fields=clazz.getDeclaredFields();

for(Fieldfield:fields){

//获得每个成员变量上面的ViewInject注解,没有的话,就会返回null

ViewInjectviewInject=field.getAnnotation(ViewInject.class);

if(viewInject!=null){

intviewId=viewInject.value();

Viewview=activity.findViewById(viewId);

try{

field.setAccessible(true);

field.set(activity,view);



}catch(IllegalAccessExceptione){

e.printStackTrace();

}

}

}

}

}

获得所有属性,然后遍历带有ViewInject注解的属性,然后拿到ViewInject注解的View的id,然后通过activity.findViewById(viewId);获得这个View。然后设置给field。

最后一个功能:EventInject

这个功能稍微麻烦一点,因为我们平时设置的点击时间是用setiOnClickListener();然后View.OnClickListener是一个接口,不能用反射来获得他的实例,那么怎么办呢?

其实我们这里可以巧妙地用动态代理来完成,当view被点击的时候,我们通过动态代理来调用onclick就行。

下面是代码:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public@interfaceOnClick{

int[]value();

}

publicclassViewInjectUtils{

publicstaticvoidinject(Activityactivity){

injectContentView(activity);

injectView(activity);

injectEvent(activity);

}



privatestaticvoidinjectEvent(finalActivityactivity){

Classclazz=activity.getClass();

Method[]methods=clazz.getDeclaredMethods();

for(finalMethodmethod2:methods){

OnClickclick=method2.getAnnotation(OnClick.class);

if(click!=null){



int[]viewId=click.value();

method2.setAccessible(true);

Objectlistener=Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(),

newClass[]{View.OnClickListener.class},newInvocationHandler(){

@Override

publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{

returnmethod2.invoke(activity,args);

}

});



try{

for(intid:viewId){

Viewv=activity.findViewById(id);

MethodsetClickListener=v.getClass().getMethod("setOnClickListener",View.OnClickListener.class);

setClickListener.invoke(v,listener);

}

}catch(Exceptione){

e.printStackTrace();

}

}

}

}

listener是一个代理对象,然后我们调用setOnClickListener的时候,把这个代理对象传进去。当发生点击的时候,就会invoke方法,这时我们就可以调用带有onClick注解的method方法了。

下面看一下运行结果:



现在我们就完成了类似butterknife的功能了,不过butterknife最新版本不是通过反射来完成的,因为反射会有性能问题,虽然现在对性能影响不大,但是作为程序员,能优化的就要尽量优化,不能只停留在能用的基础上面。

Butterknife原理

讲到butterknife的原理。这里不得不提一下一般这种注入框架都是运行时注解,即声明注解的生命周期为RUNTIME,然后在运行的时候通过反射完成注入,这种方式虽然简单,但是这种方式多多少少会有性能的损耗。那么有没有一种方法能解决这种性能的损耗呢?没错,答案肯定是有的,那就是Butterknife用的APT(AnnotationProcessingTool)编译时解析技术。

APT大概就是你声明的注解的生命周期为CLASS,然后继承AbstractProcessor类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor的process方法,对注解进行处理,那么我们就可以在处理的时候,动态生成绑定事件或者控件的java代码,然后在运行的时候,直接调用bind方法完成绑定。

其实这种方式的好处是我们不用再一遍一遍地写findViewById和onClick了,这个框架在编译的时候帮我们自动生成了这些代码,然后在运行的时候调用就行了。

源码解析

上面讲了那么多,其实都不如直接解析源码来得直接,下面我们就一步一步来探究大神怎样实现Butterknife的吧。

拿到源码的第一步是从我们调用的地方来突破,那我们就来看看程序里面是怎样调用它的呢?

@OverrideprotectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.simple_activity);

ButterKnife.setDebug(true);

ButterKnife.bind(this);



//Contrivedcodetousetheboundfields.

title.setText("ButterKnife");

subtitle.setText("FieldandmethodbindingforAndroidviews.");

footer.setText("byDaxia");

hello.setText("SayHello");



adapter=newSimpleAdapter(this);

listOfThings.setAdapter(adapter);

}

上面是github上给的例子,我们直接就从ButterKnife.bind(this)入手吧,点进来看看:

publicstaticUnbinderbind(@NonNullActivitytarget){

returnbind(target,target,Finder.ACTIVITY);

}

咦?我再点:

staticUnbinderbind(@NonNullObjecttarget,@NonNullObjectsource,@NonNullFinderfinder){

ClasstargetClass=target.getClass();

try{

ViewBinderviewBinder=findViewBinderForClass(targetClass);

returnviewBinder.bind(finder,target,source);

}catch(Exceptione){

thrownewRuntimeException("Unabletobindviewsfor"+targetClass.getName(),e);

}

}

好吧,bind方法主要就是拿到我们绑定的Activity的Class,然后找到这个Class的ViewBinder,最后调用ViewBinder的bind()方法,那么问题来了,ViewBinder是个什么鬼???我们打开

findViewBinderForClass()方法。

@NonNull

privatestaticViewBinderfindViewBinderForClass(Classcls)

throwsIllegalAccessException,InstantiationException{

ViewBinderviewBinder=BINDERS.get(cls);

if(viewBinder!=null){

returnviewBinder;

}

StringclsName=cls.getName();

try{

ClassviewBindingClass=Class.forName(clsName+"$$ViewBinder");

viewBinder=(ViewBinder)viewBindingClass.newInstance();

}catch(ClassNotFoundExceptione){

viewBinder=findViewBinderForClass(cls.getSuperclass());

}

BINDERS.put(cls,viewBinder);

returnviewBinder;

}

这里我去掉了一些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方法看看。

publicinterfaceViewBinder{

Unbinderbind(Finderfinder,Ttarget,Objectsource);

}

什么,接口???什么鬼?刚刚不是new了一个viewBinder出来么?然后这里就调用了这个viewBinder的bind方法,不行,我要看一下bind到底是什么鬼!上面说了,Butterknife用了APT技术,那么这里的viewBinder应该就是编译的时候生成的,那么我们就反编译下apk。看看到底生成了什么代码:

下面我们就先用一个简单的绑定TextView的例子,然后反编译出来看看:

publicclassMainActivityextendsAppCompatActivity{



@Bind(R.id.text_view)

TextViewtextView;



@OnClick(R.id.text_view)

voidonClick(Viewview){

textView.setText("我被click了");

}



@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

ButterKnife.bind(this);

textView.setText("我还没有被click");

}

}

源代码就这行几行,然后反编译看看:





源代码就多了一个类,MainActivity$$ViewBinder,打开看看:

publicclassMainActivity$$ViewBinder

implementsButterKnife.ViewBinder

{

publicvoidbind(ButterKnife.FinderparamFinder,finalTparamT,ObjectparamObject)

{

ViewlocalView=(View)paramFinder.findRequiredView(paramObject,2131492944,"field''textView''andmethod''onClick''");

paramT.textView=((TextView)paramFinder.castView(localView,2131492944,"field''textView''"));

localView.setOnClickListener(newDebouncingOnClickListener()

{

publicvoiddoClick(ViewparamAnonymousView)

{

paramT.onClick(paramAnonymousView);

}

});

}



publicvoidunbind(TparamT)

{

paramT.textView=null;

}

}

还记得刚刚说的,反射了一个Class$$ViewBinder么?看这里的类名。现在应该懂了吧?它刚好也是实现了ButterKnife.ViewBinder接口,我们说了,在bind方法中,最后调用了ViewBinder的bind方法,先说下几个参数paramFinder其实就是一个Finder,因为我们可以在Activity中使用butterknife,也可以在Fragment和Adapter等中使用butterknife,那么在不同的地方使用butterknife,这个Finder也就不同。在Activity中,其实源码就是这样子的:

ACTIVITY{

@OverrideprotectedViewfindView(Objectsource,intid){

return((Activity)source).findViewById(id);

}



@OverridepublicContextgetContext(Objectsource){

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方法:

@Overridepublicsynchronizedvoidinit(ProcessingEnvironmentenv){

super.init(env);



elementUtils=env.getElementUtils();

typeUtils=env.getTypeUtils();

filer=env.getFiler();

}

ProcessingEnviroment参数提供很多有用的工具类Elements,Types和Filer。Types是用来处理TypeMirror的工具类,Filer用来创建生成辅助文件。至于ElementUtils嘛,其实ButterKnifeProcessor在运行的时候,会扫描所有的Java源文件,然后每一个Java源文件的每一个部分都是一个Element,比如一个包、类或者方法。

@OverridepublicSetgetSupportedAnnotationTypes(){

Settypes=newLinkedHashSet<>();



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(Classlistener:LISTENERS){

types.add(listener.getCanonicalName());

}



returntypes;

}

getSupportedAnnotationTypes()方法主要是指定ButterknifeProcessor是注册给哪些注解的。我们可以看到,在源代码里面,作者一个一个地把Class文件加到那个LinkedHashSet里面,然后再把LISTENERS也全部加进去。

其实整个类最重要的是process方法:

@Overridepublicbooleanprocess(Setelements,RoundEnvironmentenv){

MaptargetClassMap=findAndParseTargets(env);



for(Map.Entryentry:targetClassMap.entrySet()){

TypeElementtypeElement=entry.getKey();

BindingClassbindingClass=entry.getValue();



try{

bindingClass.brewJava().writeTo(filer);

}catch(IOExceptione){

error(typeElement,"Unabletowriteviewbinderfortype%s:%s",typeElement,

e.getMessage());

}

}

returntrue;

}

这个方法的作用主要是扫描、评估和处理我们程序中的注解,然后生成Java文件。也就是前面说的ViewBinder。首先一进这个函数就调用了findAndParseTargets方法,我们就去看看findAndParseTargets方法到底做了什么:

privateMapfindAndParseTargets(RoundEnvironmentenv){

MaptargetClassMap=newLinkedHashMap<>();

SeterasedTargetNames=newLinkedHashSet<>();



//Processeach@BindViewelement.

for(Elementelement:env.getElementsAnnotatedWith(BindView.class)){

if(!SuperficialValidation.validateElement(element))continue;

try{

parseBindView(element,targetClassMap,erasedTargetNames);

}catch(Excwww.sm136.comeptione){

logParsingError(element,BindView.class,e);

}

}



Observable.from(topLevelClasses)

.flatMap(newFunc1>(){

@OverridepublicObservablecall(BindingClasstopLevelClass){

if(topLevelClass.hasViewBindings()){

//Ithasanunbinderclassanditwillalsobethehighestunbinderclassforall

//descendants.

topLevelClass.setHighestUnbinderClassName(topLevelClass.getUnbinderClassName());

}else{

//Nounbinderclass,sonullitoutsoweknowwecanjustreturntheNOPunbinder.

topLevelClass.setUnbinderClassName(null);

}



//Recursivelysetupparentunbindingrelationshipsonallitsdescendants.

returnButterKnifeProcessor.this.setParentUnbindingRelationships(

topLevelClass.getDescendants());

}

})

.toCompletable()

.await();



returntargetClassMap;

}

这里代码炒鸡多,我就不全部贴出来了,只贴出来一部分,这个方法最后还用了rxjava的样子。这个方法的主要的流程如下:

扫描所有具有注解的类,然后根据这些类的信息生成BindingClass,最后生成以TypeElement为键,BindingClass为值的键值对。

循环遍历这个键值对,根据TypeElement和BindingClass里面的信息生成对应的java类。例如AnnotationActivity生成的类即为Cliass$$ViewBinder类。

因为我们之前用的例子是绑定的一个View,所以我们就只贴了解析View的代码。好吧,这里遍历了所有带有@BindView的Element,然后对每一个Element进行解析,也就进入了parseBindView这个方法中:

privatevoidparseBindView(Elementelement,MaptargetClassMap,

SeterasedTargetNames){

TypeElementenclosingElement=(TypeElement)element.getEnclosingElement();



//Startbyverifyingcommongeneratedcoderestrictions.

booleanhasError=isInaccessibleViaGeneratedCode(BindView.class,"fields",element)

||isBindingInWrongPackage(BindView.class,element);



//VerifythatthetargettypeextendsfromView.

TypeMirrorelementType=element.asType();

if(elementType.getKind()==TypeKind.TYPEVAR){

TypeVariabletypeVariable=(TypeVariable)elementType;

elementType=typeVariable.getUpperBound();

}

if(!isSubtypeOfType(elementType,VIEW_TYPE)&&!isInterface(elementType)){

error(element,"@%sfieldsmustextendfromVieworbeaninterface.(%s.%s)",

BindView.class.getSimpleName(),enclosingElement.getQualifiedName(),

element.getSimpleName());

hasError=true;

}



if(hasError){

return;

}



//Assembleinformationonthefield.

intid=element.getAnnotation(BindView.class).value();



BindingClassbindingClass=targetClassMap.get(enclosingElement);

if(bindingClass!=null){

ViewBindingsviewBindings=bindingClass.getViewBinding(id);

if(viewBindings!=null){

Iteratoriterator=viewBindings.getFieldBindings().iterator();

if(iterator.hasNext()){

FieldViewBindingexistingBinding=iterator.next();

error(element,"Attempttouse@%sforanalreadyboundID%don''%s''.(%s.%s)",

BindView.class.getSimpleName(),id,existingBinding.getName(),

enclosingElement.getQualifiedName(),element.getSimpleName());

return;

}

}

}else{

bindingClass=getOrCreateTargetClass(targetClassMap,enclosingElement);

}



Stringname=element.getSimpleName().toString();

TypeNametype=TypeName.get(elementType);

booleanrequired=isFieldRequired(element);



FieldViewBindingbinding=newFieldViewBinding(name,type,required);

bindingClass.addField(id,binding);



//Addthetype-erasedversiontothevalidbindingtargetsset.

erasedTargetNames.add(enclosingElement);

}

然后这里从一进入这个方法到

intid=element.getAnnotation(BindView.class).value();

都是在拿到注解信息,然后验证注解的target的类型是否继承自view,然后上面这一行代码获得我们要绑定的View的id,再从targetClassMap里面取出BindingClass(这个BindingClass是管理了所有关于这个注解的一些信息还有实例本身的信息,其实最后是通过BindingClass来生成java代码的),如果targetClassMap里面不存在的话,就在

bindingClass=getOrCreateTargetClass(targetClassMap,enclosingElement);

这里生成一个,我们进去看一下getOrCreateTargetClass:

privateBindingClassgetOrCreateTargetClass(MaptargetClassMap,

TypeElementenclowww.shanxiwang.netsingElement){

BindingClassbindingClass=targetClassMap.get(enclosingElement);

if(bindingClass==null){

StringtargetType=enclosingElement.getQualifiedName().toString();

StringclassPackage=getPackageName(enclosingElement);

booleanisFinal=enclosingElement.getModifiers().contains(Modifier.FINAL);

StringclassName=getClassName(enclosingElement,classPackage)+BINDING_CLASS_SUFFIX;

StringclassFqcn=getFqcn(enclosingElement)+BINDING_CLASS_SUFFIX;



bindingClass=newBindingClass(classPackage,className,isFinal,targetType,classFqcn);

targetClassMap.put(enclosingElement,bindingClass);

}

returnbindingClass;

}

这里面其实很简单,就是获取一些这个注解所修饰的变量的一些信息,比如类名呀,包名呀,然后className这里就赋值成Class$$ViewHolder了,因为:

privatestaticfinalStringBINDING_CLASS_SUFFIX="$$ViewBinder";

然后把这个解析后的bindingClass加入到targetClassMap里面。

返回刚刚的parseBindView中,根据view的信息生成一个FieldViewBinding,最后添加到上边生成的BindingClass实例中。这里基本完成了解析工作。最后回到findAndParseTargets中:

Observable.from(topLevelClasses)

.flatMap(newFunc1>(){

@OverridepublicObservablecall(BindingClasstopLevelClass){

if(topLevelClass.hasViewBindings()){

topLevelClass.setHighestUnbinderClassName(topLevelClass.getUnbinderClassName());

}else{



topLevelClass.setUnbinderClassName(null);

}

returnButterKnifeProcessor.this.setParentUnbindingRelationships(

topLevelClass.getDescendants());

}

})

.toCompletable()

.await();

这里用到了rxjava,其实这里主要的工作是建立上面的绑定的所有的实例的解绑的关系,因为我们绑定了,最后在代码中还是会解绑的。这里预先处理好了这些关系。因为这里要递归地完成解绑,所以用了flatmap,flatmap把每一个创建出来的Observable发送的事件,都集中到同一个Observable中,然后这个Observable负责将这些事件统一交给Subscriber。

然而这部分涉及到很多rxjava的东西,有兴趣的童鞋去看看大神的写给android开发者的RxJava详解这篇文章,然后再来看这里就很轻松了。

回到我们的process中,现在解析完了annotation,该生成java文件了,我再把代码贴一下:

@Overridepublicbooleanprocess(Setelements,RoundEnvironmentenv){

MaptargetClassMap=findAndParseTargets(env);



for(Map.Entryentry:targetClassMap.entrySet()){

TypeElementtypeElement=entry.getKey();

BindingClassbindingClass=entry.getValue();



try{

bindingClass.brewJava().writeTo(filer);

}catch(IOExceptione){

error(typeElement,"Unabletowriteviewbinderfortype%s:%s",typeElement,

e.getMessage());

}

}



returntrue;

}

遍历刚刚得到的targetClassMap,然后再一个一个地通过

bindingClass.brewJava().writeTo(filer);

来生成java文件。然而生成的java文件也是根据上面的信息来用字符串拼接起来的,然而这个工作在brewJava()中完成了:

JavaFilebrewJava(){

TypeSpec.Builderresult=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()){

//Createunbindingclass.

result.addType(createUnbinderClass());



if(!isFinal){

//Nowweneedtoprovidechildclassestoaccessandoverrideunbinderimplementations.

createUnbinderCreateUnbinderMethod(result);

}

}



returnJavaFile.builder(classPackage,result.build())

.addFileComment("GeneratedcodefromButterKnife.Donotmodify!")

.build();

}

最后通过writeTo(Filerfiler)生成java源文件。

献花(0)
+1
(本文系网络学习天...首藏)