




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【移动应用开发技术】分分钟带你读懂ButterKnife的源码
为什么要写这一系列的博客呢?
为什么要写这一系列的博客呢?因为在Android开发的过程中,泛型,反射,注解这些知识进场会用到,几乎所有的框架至少都会用到上面的一两种知识,如Gson就用到泛型,反射,注解,Retrofit也用到泛型,反射,注解。学好这些知识对我们进阶非常重要,尤其是阅读开源框架源码或者自己开发开源框架。前言ButterKnife这个开源库火了有一段时间了,刚开始它的实现原理是使用反射实现的,性能较差。再后面的版本中逐渐使用注解+放射实现,性能提高了不少。ButterKnife是基于编译时的框架,它能够帮助我们减去每次写FindViewById的麻烦,截止到2017.5.1,在github上面的start已经超过15000.本篇博客要分析的ButterKnife的源码主要包括以下三个部分,版本号是8.5.1其中butterknife-annotations库主要用来存放自定义注解;butterknife-compiler主要是用来扫描哪些地方使用到我们的自定义注解,并进行相应的处理,生成模板代码等;butterknife主要是用来注入我们的代码的。我们先来先一下要怎样使用butterknife:ButterKnife的基本使用在moudle的build.gradle增加依赖dependencies{
compile'com.jakewharton:butterknife:8.5.1'
annotationProcessor'com.jakewharton:butterknife-compiler:8.5.1'
}publicclassSimpleActivityextendsActivity{
privatestaticfinalButterKnife.Action<View>ALPHA_FADE=newButterKnife.Action<View>(){
@Overridepublicvoidapply(@NonNullViewview,intindex){
AlphaAnimationalphaAnimation=newAlphaAnimation(0,1);
alphaAnimation.setFillBefore(true);
alphaAnimation.setDuration(500);
alphaAnimation.setStartOffset(index*100);
view.startAnimation(alphaAnimation);
}
};
@BindView(R2.id.title)TextViewtitle;
@BindView(R2.id.subtitle)TextViewsubtitle;
@BindView(R2.id.hello)Buttonhello;
@BindView(R2.id.list_of_things)ListViewlistOfThings;
@BindView(R2.id.footer)TextViewfooter;
@BindViews({R2.id.title,R2.id.subtitle,R2.id.hello})List<View>headerViews;
privateSimpleAdapteradapter;
@OnClick(R2.id.hello)voidsayHello(){
Toast.makeText(this,"Hello,views!",LENGTH_SHORT).show();
ButterKnife.apply(headerViews,ALPHA_FADE);
}
@OnLongClick(R2.id.hello)booleansayGetOffMe(){
Toast.makeText(this,"Letgoofme!",LENGTH_SHORT).show();
returntrue;
}
@OnItemClick(R2.id.list_of_things)voidonItemClick(intposition){
Toast.makeText(this,"Youclicked:"+adapter.getItem(position),LENGTH_SHORT).show();
}
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
//Contrivedcodetousetheboundfields.
title.setText("ButterKnife");
subtitle.setText("FieldandmethodbindingforAndroidviews.");
footer.setText("byJakeWharton");
hello.setText("SayHello");
adapter=newSimpleAdapter(this);
listOfThings.setAdapter(adapter);
}
}调用gradlebuild命令,我们在相应的目录下将可以看到生成类似这样的代码。publicclassSimpleActivity_ViewBinding<TextendsSimpleActivity>implementsUnbinder{
protectedTtarget;
privateViewview2130968578;
privateViewview2130968579;
@UiThread
publicSimpleActivity_ViewBinding(finalTtarget,Viewsource){
this.target=target;
Viewview;
target.title=Utils.findRequiredViewAsType(source,R.id.title,"field'title'",TextView.class);
target.subtitle=Utils.findRequiredViewAsType(source,R.id.subtitle,"field'subtitle'",TextView.class);
view=Utils.findRequiredView(source,R.id.hello,"field'hello',method'sayHello',andmethod'sayGetOffMe'");
target.hello=Utils.castView(view,R.id.hello,"field'hello'",Button.class);
view2130968578=view;
view.setOnClickListener(newDebouncingOnClickListener(){
@Override
publicvoiddoClick(Viewp0){
target.sayHello();
}
});
view.setOnLongClickListener(newView.OnLongClickListener(){
@Override
publicbooleanonLongClick(Viewp0){
returntarget.sayGetOffMe();
}
});
view=Utils.findRequiredView(source,R.id.list_of_things,"field'listOfThings'andmethod'onItemClick'");
target.listOfThings=Utils.castView(view,R.id.list_of_things,"field'listOfThings'",ListView.class);
view2130968579=view;
((AdapterView<?>)view).setOnItemClickListener(newAdapterView.OnItemClickListener(){
@Override
publicvoidonItemClick(AdapterView<?>p0,Viewp1,intp2,longp3){
target.onItemClick(p2);
}
});
target.footer=Utils.findRequiredViewAsType(source,R.id.footer,"field'footer'",TextView.class);
target.headerViews=Utils.listOf(
Utils.findRequiredView(source,R.id.title,"field'headerViews'"),
Utils.findRequiredView(source,R.id.subtitle,"field'headerViews'"),
Utils.findRequiredView(source,R.id.hello,"field'headerViews'"));
}
@Override
@CallSuper
publicvoidunbind(){
Ttarget=this.target;
if(target==null)thrownewIllegalStateException("Bindingsalreadycleared.");
target.title=null;
target.subtitle=null;
target.hello=null;
target.listOfThings=null;
target.footer=null;
target.headerViews=null;
view2130968578.setOnClickListener(null);
view2130968578.setOnLongClickListener(null);
view2130968578=null;
((AdapterView<?>)view2130968579).setOnItemClickListener(null);
view2130968579=null;
this.target=null;
}
}ButterKnife的执行流程总的来说,大概可以分为以下几步:第一步:在编译的时候扫描注解,并做相应的处理,生成java代码。这一步,可以拆分为几个小步骤:butterknife-annotations讲解我们知道ButterKnife自定义很多的注解,有BindArray,BindBitmap,BindColor,BindView等,这里我们以BindView为例子讲解就OK了,其他的也是基本类似的,这里就不再讲解了。//编译时注解
@Retention(CLASS)
//成员变量,(includesenumconstants)
@Target(FIELD)
public@interfaceBindView{
/**ViewIDtowhichthefieldwillbebound.*/
@IdResintvalue();
}Processor解析器说明我们先来看一些基本方法:在init方法里面得到一些辅助工具类,这样有一个好处,确保工具类是单例的,因为init方法只会在初始化的时候调用。publicsynchronizedvoidinit(ProcessingEnvironmentenv){
super.init(env);
//辅助工具类
elementUtils=env.getElementUtils();
typeUtils=env.getTypeUtils();
filer=env.getFiler();
}接着重写getSupportedAnnotationTypes方法,返回我们支持的注解类型。@Override
publicSet<String>getSupportedAnnotationTypes(){
Set<String>types=newLinkedHashSet<>();
for(Class<?extendsAnnotation>annotation:getSupportedAnnotations()){
types.add(annotation.getCanonicalName());
}
//返回支持注解的类型
returntypes;
}
privateSet<Class<?extendsAnnotation>>getSupportedAnnotations(){
Set<Class<?extendsAnnotation>>annotations=newLinkedHashSet<>();
annotations.add(BindArray.class);
annotations.add(BindBitmap.class);
annotations.add(BindBool.class);
annotations.add(BindColor.class);
annotations.add(BindDimen.class);
annotations.add(BindDrawable.class);
annotations.add(BindFloat.class);
annotations.add(BindInt.class);
annotations.add(BindString.class);
annotations.add(BindView.class);
annotations.add(BindViews.class);
annotations.addAll(LISTENERS);
returnannotations;
}接下来来看我们的重点,process方法。所做的工作大概就是拿到我们所有的注解信息,存进map集合,遍历map集合,做相应的处理,生成java代码。@Override
publicbooleanprocess(Set<?extendsTypeElement>elements,RoundEnvironmentenv){
//拿到所有的注解信息,TypeElement作为key,BindingSet作为value
Map<TypeElement,BindingSet>bindingMap=findAndParseTargets(env);
//遍历map里面的所有信息,并生成java代码
for(Map.Entry<TypeElement,BindingSet>entry:bindingMap.entrySet()){
TypeElementtypeElement=entry.getKey();
BindingSetbinding=entry.getValue();
JavaFilejavaFile=binding.brewJava(sdk);
try{
javaFile.writeTo(filer);
}catch(IOExceptione){
error(typeElement,"Unabletowritebindingfortype%s:%s",typeElement,e
.getMessage());
}
}
returnfalse;
}这里我们进入findAndParseTargets方法,看里面到底是怎样将注解信息存进map集合的?findAndParseTargets方法里面针对每一个自定义注解(BindArray,BindBitmap,BindColor,BindView)等都做了处理,这里我们重点关注@BindView的处理即可。其他注解的处理思想也是一样的。我们先来看一下findAndParseTargets方法的前半部分,遍历env.getElementsAnnotatedWith(BindView.class)集合,并调用parseBindView方法去转化。privateMap<TypeElement,BindingSet>findAndParseTargets(RoundEnvironmentenv){
Map<TypeElement,BindingSet.Builder>builderMap=newLinkedHashMap<>();
Set<TypeElement>erasedTargetNames=newLinkedHashSet<>();
scanForRClasses(env);
//Processeach@BindViewelement.
for(Elementelement:env.getElementsAnnotatedWith(BindView.class)){
//wedon'tSuperficialValidation.validateElement(element)
//sothatanunresolvedViewtypecanbegeneratedbylaterprocessingrounds
try{
parseBindView(element,builderMap,erasedTargetNames);
}catch(Exceptione){
logParsingError(element,BindView.class,e);
}
}
//后半部分,待会再讲
}可以看到牵绊部分的主要逻辑在parseBindView方法里面,主要做了以下几步操作:判断被注解@BindView修饰的成员变量是不是合法的,private或者static修饰的,则出错。parseBindView方法分析完毕之后,我们在回过头来看一下findAndParseTargets方法的后半部分,主要做的工作是对bindingMap进行重排序。到这里为止,我们已经分析完ButterKnifeProcessor是怎样处理注解的相关知识,并存进map集合中的,下面我们回到process方法,看一下是怎样生成java模板代码的。生成代码的核心代码只有这几行跟踪进去,发现是调用square公司开源的库javapoet开生成代码的。关于javaPoet的使用可以参考官网地址privateTypeSpeccreateType(intsdk){TypeSpec.Builderresult=TypeSpec.classBuilder(bindingClassName.simpleName()).addModifiers(PUBLIC);if(isFinal){result.addModifiers(FINAL);}if(parentBinding!=null){
result.superclass(parentBinding.bindingClassName);
}else{
result.addSuperinterface(UNBINDER);
}
if(hasTargetField()){
result.addField(targetTypeName,"target",PRIVATE);
}
//如果是View或者是View的子类的话,添加构造方法
if(isView){
result.addMethod(createBindingConstructorForView());
}elseif(isActivity){//如果是Activity或者是Activity的子类的话,添加构造方法
result.addMethod(createBindingConstructorForActivity());
}elseif(isDialog){//如果是Dialog或者是Dialog的子类的话,添加构造方法
result.addMethod(createBindingConstructorForDialog());
}
//如果构造方法不需要View参数,添加需要View参数的构造方法
if(!constructorNeedsView()){
//Addadelegatingconstructorwithatargettype+viewsignatureforreflectiveuse.
result.addMethod(createBindingViewDelegateConstructor());
}
result.addMethod(createBindingConstructor(sdk));
if(hasViewBindings()||parentBinding==null){
//生成unBind方法
result.addMethod(createBindingUnbindMethod(result));
}
returnresult.build();}接着我们一起来看一下createBindingConstructor(sdk)方法,大概做的事情就是
-判断是否有设置监听,如果有监听,将View设置为final
-遍历viewBindings,调用addViewBinding生成findViewById形式的代码。privateMethodSpeccreateBindingConstructor(intsdk){MethodSpec.Builderconstructor=MethodSpec.constructorBuilder().addAnnotation(UI_THREAD).addModifiers(PUBLIC);//如果有方法绑定,比如@onClick,那么增加一个targetTypeName类型的方法参数target,并且是final类型的if(hasMethodBindings()){constructor.addParameter(targetTypeName,"target",FINAL);}else{//如果没有,不是final类型的constructor.addParameter(targetTypeName,"target");}//如果有注解的View,那么添加VIEW类型source参数if(constructorNeedsView()){constructor.addParameter(VIEW,"source");}else{//添加Context类型的context参数constructor.addParameter(CONTEXT,"context");}if(hasUnqualifiedResourceBindings()){
//AaptcanchangeIDsoutfromunderneathus,justsuppresssinceallwillworkat
//runtime.
constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value","$S","ResourceType")
.build());
}
//如果@OnTouch绑定View,添加@SuppressLint("ClickableViewAccessibility")
if(hasOnTouchMethodBindings()){
constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT)
.addMember("value","$S","ClickableViewAccessibility")
.build());
}
//如果parentBinding不为空,调用父类的构造方法
if(parentBinding!=null){
if(parentBinding.constructorNeedsView()){
constructor.addStatement("super(target,source)");
}elseif(constructorNeedsView()){
constructor.addStatement("super(target,source.getContext())");
}else{
constructor.addStatement("super(target,context)");
}
constructor.addCode("\n");
}
//添加成员变量
if(hasTargetField()){
constructor.addStatement("this.target=target");
constructor.addCode("\n");
}
if(hasViewBindings()){
if(hasViewLocal()){
//Localvariableinwhichallviewswillbetemporarilystored.
constructor.addStatement("$Tview",VIEW);
}
//遍历viewBindings,生成source.findViewById($L)代码
for(ViewBindingbinding:viewBindings){
addViewBinding(constructor,binding);
}
for(FieldCollectionViewBindingbinding:collectionBindings){
constructor.addStatement("$L",binding.render());
}
if(!resourceBindings.isEmpty()){
constructor.addCode("\n");
}
}
if(!resourceBindings.isEmpty()){
if(constructorNeedsView()){
constructor.addStatement("$Tcontext=source.getContext()",CONTEXT);
}
if(hasResourceBindingsNeedingResource(sdk)){
constructor.addStatement("$Tres=context.getResources()",RESOURCES);
}
for(ResourceBindingbinding:resourceBindings){
constructor.addStatement("$L",binding.render(sdk));
}
}
returnconstructor.build();}下面我们一起来看一下addViewBinding方法是怎样生成代码的。
privatevoidaddViewBinding(MethodSpec.Builderresult,ViewBindingbinding){if(binding.isSingleFieldBinding()){//Optimizethecommoncasewherethere'sasinglebindingdirectlytoafield.FieldViewBindingfieldBinding=binding.getFieldBinding();//注意这里直接使用了target.的形式,所以属性肯定是不能private的CodeBlock.Builderbuilder=CodeBlock.builder().add("target.$L=",fieldBinding.getName());booleanrequiresCast=requiresCast(fieldBinding.getType());
if(!requiresCast&&!fieldBinding.isRequired()){
builder.add("source.findViewById($L)",binding.getId().code);
}else{
builder.add("$T.find",UTILS);
builder.add(fieldBinding.isRequired()?"RequiredView":"OptionalView");
if(requiresCast){
builder.add("AsType");
}
builder.add("(source,$L",binding.getId().code);
if(fieldBinding.isRequired()||requiresCast){
builder.add(",$S",asHumanDescription(singletonList(fieldBinding)));
}
if(requiresCast){
builder.add(",$T.class",fieldBinding.getRawType());
}
builder.add(")");
}
result.addStatement("$L",builder.build());
return;
}
**ButterKnife是怎样实现代码注入的**
使用过ButterKnife得人基本都知道,我们是通过bind方法来实现注入的,即自动帮我们findViewById,解放我们的双手,提高工作效率。下面我们一起来看一下bind方法是怎样实现注入的。@NonNull@UiThreadbr/>@UiThreadViewsourceView=target.getWindow().getDecorView();returncreateBinding(target,sourceView);}可以看到bind方法很简单,逻辑基本都交给createBinding方法去完成。我们一起进入createBinding方法来看一下到底做了什么privatestaticUnbindercreateBinding(@NonNullObjecttarget,@NonNullViewsource){Class<?>targetClass=target.getClass();if(debug)Log.d(TAG,"Lookingupbindingfor"+targetClass.getName());//从Class中查找constructorConstructor<?extendsUnbinder>constructor=findBindingConstructorForClass(targetClass);if(constructor==null){
returnUnbinder.EMPTY;
}
//noinspectionTryWithIdenticalCatchesResolvestoAPI19+onlytype.
try{
//反射实例化构造方法
returnconstructor.newInstance(target,source);
}catch(IllegalAccessExceptione){
thrownewRuntimeException("Unabletoinvoke"+constructor,e);
}catch(InstantiationExceptione){
thrownewRuntimeException("Unabletoinvoke"+constructor,e);
}catch(InvocationTargetExceptione){
Throwablecause=e.getCause();
if(causeinstanceofRuntimeException){
throw(RuntimeException)cause;
}
if(causeinstanceofError){
throw(Error)cause;
}
thrownewRuntimeException("Unabletocreatebindinginstance.",cause);
}}其实createBinding来说,主要做了这几件事情
-传入class,通过findBindingConstructorForClass方法来实例化constructor
-利用反射来初始化constructor对
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 便利店业绩考核合同样本
- 2025-2030中国非有机硅离型纸行业市场发展趋势与前景展望战略研究报告
- 2025-2030中国零售货架系统行业市场发展趋势与前景展望战略研究报告
- 2025-2030中国降度酒行业市场发展分析及前景趋势与投资研究报告
- 2025-2030中国防水表行业市场现状供需分析及投资评估规划分析研究报告
- 2025-2030中国防护保养品行业市场深度调研及发展趋势与投资前景研究报告
- 2025-2030中国镇静催眠药行业市场深度调研及需求趋势与投资研究报告
- 2025-2030中国锯线行业市场现状供需分析及投资评估规划分析研究报告
- 2025-2030中国锌空电池市场运行风险与重点企业发展分析研究报告
- 2025-2030中国银饰行业市场发展分析及前景趋势与投资研究报告
- 计算猪单位体重总产热量的计算公式
- 2023年4月自考00540外国文学史试题及答案含评分标准
- 中国志愿服务发展指数报告
- 畜牧业经营预测与决策 畜牧业经营预测(畜牧业经营管理)
- MBTI 英文介绍课件
- 认识平面图上的方向
- 液氮安全培训资料课件
- 陕西省公务员招聘面试真题和考官题本及答案102套
- 铁路工务巡道工岗位作业标准(岗位职责、岗位风险)
- 幼儿园红色故事绘本:《鸡毛信》 课件
- 监理毕业论文开题报告(文献综述+计划书),开题报告
评论
0/150
提交评论