一. ButterKnife介绍

在Android编程过程中,我们会写大量的布局和点击事件,像初始view、设置view监听这样简单而重复的操作,这些代码繁琐而又不雅观,比如:

TextViewtvSetName=findViewById(R.id.xxx);tvSetName.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(Viewv){//xxxx}});TextViewtvSetAge=findViewById(R.id.xxx);tvSetAge.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(Viewv){//xxxx}});TextViewtvSetArea=findViewById(R.id.xxx);tvSetArea.setOnClickListener(newView.OnClickListener(){@OverridepublicvoidonClick(Viewv){//xxxx}});



Activity中这种代码多了之后,很不雅观。


二. 使用简介

ButterKnife使用方法比较简单,主要包括以下步骤:


引用ButterKnife包

在onCreate里面bind(setContentView之后)

绑定各种事件

onDestroy里面解绑释放资源


ButterKnife使用方法


三. ButterKnife源代码下载


ButterKnife github源代码地址


直接git clone或者下载zip即可。


四. 编译

Android studio打开ButterKnife源代码

AndroidStudio->File->open->ButterKnife源代码路径->确认

Build->Rebuild Project



五. 生成的aar和jar包

生成的包主要有两个

butterknife-annotations-8.5.2-SNAPSHOT.jar

路径:butterknife-annotations->build->libs


butterknife-release.aar

路径: butterknife->build->outputs->aar



六. 其他应用引用自定义ButterKnife包

删除原来ButterKnife包引用,因为要使用自己编译的包

//compile'com.jakewharton:butterknife:8.4.0'




拷贝文件

拷贝上面两个文件到自己项目app模块的libs 目录


添加aar的关联

打开app模块的build.gradle文件,添加:


compilefileTree(include:['*.jar'],dir:'libs')compile(name:'butterknife-release',ext:'aar')

Build->Rebuild Project



七. 源代码分析


1. ButterKnife.bind(Activity target)过程


文件名:ButterKnife.java

staticfinalMap<Class<?>,Constructor<?extendsUnbinder>>BINDINGS=newLinkedHashMap<>();publicstaticUnbinderbind(@NonNullActivitytarget){Log.d("Sandy","ButterKnifebind..target:"+target);ViewsourceView=target.getWindow().getDecorView();returncreateBinding(target,sourceView);}privatestaticUnbindercreateBinding(@NonNullObjecttarget,@NonNullViewsource){Class<?>targetClass=target.getClass();if(debug)Log.d(TAG,"Lookingupbindingfor"+targetClass.getName());Constructor<?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);}}privatestaticConstructor<?extendsUnbinder>findBindingConstructorForClass(Class<?>cls){Constructor<?extendsUnbinder>bindingCtor=BINDINGS.get(cls);if(bindingCtor!=null){if(debug)Log.d(TAG,"HIT:Cachedinbindingmap.");returnbindingCtor;}StringclsName=cls.getName();if(clsName.startsWith("android.")||clsName.startsWith("java.")){if(debug)Log.d(TAG,"MISS:Reachedframeworkclass.Abandoningsearch.");returnnull;}try{Log.d("Sandy","findBindingConsForClass:"+clsName+"vindBindingname:"+clsName+"_ViewBinding");Class<?>bindingClass=cls.getClassLoader().loadClass(clsName+"_ViewBinding");//noinspectionuncheckedbindingCtor=(Constructor<?extendsUnbinder>)bindingClass.getConstructor(cls,View.class);if(debug)Log.d(TAG,"HIT:Loadedbindingclassandconstructor.");}catch(ClassNotFoundExceptione){if(debug)Log.d(TAG,"Notfound.Tryingsuperclass"+cls.getSuperclass().getName());bindingCtor=findBindingConstructorForClass(cls.getSuperclass());}catch(NoSuchMethodExceptione){thrownewRuntimeException("Unabletofindbindingconstructorfor"+clsName,e);}BINDINGS.put(cls,bindingCtor);returnbindingCtor;}




上面这段代码有几个注意点:

a. sourceView代表是DecorView,也就是我们窗口的顶级View。


b. findBindingConstructorForClass有个BINDINGS缓存,key是class,value是缓存的Unbinder对象,这样做可以加快bind速度。

因为每个类的ButterKnife注解在运行期间是不会变的,比如MainActivity有3个ButterKnife注解,那么它就是3个。除非有新的apk安装。

所以适合用缓存来实现。


c.findBindingConstructorForClass使用了递归的方法

这个方法使用了递归,不断调用父类,也就是


catch(ClassNotFoundExceptione){if(debug)Log.d(TAG,"Notfound.Tryingsuperclass"+cls.getSuperclass().getName());bindingCtor=findBindingConstructorForClass(cls.getSuperclass());}



那为什么要这么处理呢?


因为有些Activity没有ButterKnife的注解,但是它的父类可能有,比如BaseActivity。所以需要往上递归。那什么时候递归结束呢?


if(bindingCtor!=null){if(debug)Log.d(TAG,"HIT:Cachedinbindingmap.");returnbindingCtor;}StringclsName=cls.getName();if(clsName.startsWith("android.")||clsName.startsWith("java.")){if(debug)Log.d(TAG,"MISS:Reachedframeworkclass.Abandoningsearch.");returnnull;}



如果缓存里面找到了结果,那么结束,同时返回结果;


或者类名以"android."或者"java."开头,也结束,返回null;

以Activity为例,Activity的类名是android.app.Activity,所以你的MainActivity如果递归到Activity还没有找到ButterKnife注解,那就说明你的MainActivity是没有包含ButterKnife注解的。


d. 如果子Activity和父Activity都有ButterKnife注解怎么办?

答案是返回子Activity以及其对应的Constructor<? extends Unbinder> bindingCtor对象


那它的父Activity如果也有ButterKnife注解怎么办?怎么解析父Activity的ButterKnife注解呢? 这个问题我们待会再讲。

记为问题1。


e.Constructor<? extends Unbinder> 是个什么东西?

调用ButterKnife.bind(Activity target)方法后会返回一个Unbinder对象,可以在onDestroy中调用unbind()方法,那个Unbinder是什么东西呢?这个问题待会再讲。

记为问题2.


f. clsName + "_ViewBinding"是什么类?

Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");

这个问题记为问题3.


2. ButterKnifeProcessor.java

这个类是ButterKnife里面很重要的一个类了,它继承自AbstractProcessor。


看来不懂的问题越来越多,那么有必要来学习下Java注解的知识。



八. Java注解


在分析ButterKnife代码前,需要了解Java的注解,需要了解Annotation Processor,因为ButterKnifer用到这个知识。


这里面很重要的一个知识点就是你可以编写一定的规则,让它在应用程序编译时执行你的规则,然后生成Java代码;并且生成的Java还可以参与编译。


Java注解



九. 自己定义的注解框架


1. Eclipse实现


主要是参考这篇帖子完成的,大家可以参考这篇帖子:

Eclipse中使用Java注解Processor


主要说下不同的地方:

a. source folder的创建,直接File->New->source folder一直创建不成功,后面用另外一种方法创建成功了。

项目->右击->Properties->Java Build Path->Source->Add Folder->Create New Folder->输入resources/META-INF/services->finish->ok->ok



2. Android studio实现


AndroidStudio下面使用Java注解Processor




十. 调试自己的自定义框架

有个时候需要调试自己写的框架是否正常运行,下面介绍下调试:


1. 在项目gradle.properties里面添加

org.gradle.daemon=trueorg.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8011


2.Edit Configureations


3. 增加远程调试


4. 启动远程调试



下面的控制台会出现下面的提示:

Connected to the target VM, address: 'localhost:8011', transport: 'socket'


5. 打断点

在Processor里面打上断点,比如init, process


6. 连上手机,项目根目录命令行下执行

gradle clean connectedCheck



十一. ButterKnife使用Java注解

理解了Java注解Processor之后,ButterKnife就比较好理解了。


首先它的ButterKnifeProcessor.java继承自AbstractProcessor,重写了init和process之类的方法。

也就是说它在编译的时候会被执行,生成辅助代码。


它的辅助代码生成到哪里了呢?


在我们自己的应用程序里面搜索_ViewBinding,就可以找到已经生成好的辅助类,如下:

publicclassxxxx_ViewBinding<TextendsLoginCloudActivity>extendsBaseActivity_ViewBinding<T>{privateViewview2131689593;@UiThreadpublicxxxx_ViewBinding(finalTtarget,Viewsource){super(target,source);Viewview;target.mEtUser=Utils.findRequiredViewAsType(source,R.id.et_user,"field'mEtUser'",EditText.class);target.mEtPwd=Utils.findRequiredViewAsType(source,R.id.et_pwd,"field'mEtPwd'",EditText.class);view=Utils.findRequiredView(source,R.id.btn_login,"method'btn_login'andmethod'btn_login_long'");view2131689593=view;view.setOnClickListener(newDebouncingOnClickListener(){@OverridepublicvoiddoClick(Viewp0){target.btn_login();}});view.setOnLongClickListener(newView.OnLongClickListener(){@OverridepublicbooleanonLongClick(Viewp0){returntarget.btn_login_long();}});}@Overridepublicvoidunbind(){Ttarget=this.target;super.unbind();target.mEtUser=null;target.mEtPwd=null;view2131689593.setOnClickListener(null);view2131689593.setOnLongClickListener(null);view2131689593=null;}}


这个类在编译的时候会被自动生成,那么在运行的时候,它会被调用。

这个类的构造函数会去初始化那些控件,设置监听。


回到第七步 ButterKnife.bind()的过程


在createBinding的时候,它会初始化这个xxx_ViewBinding类,如下:

privatestaticUnbindercreateBinding(@NonNullObjecttarget,@NonNullViewsource){Class<?>targetClass=target.getClass();if(debug)Log.d(TAG,"Lookingupbindingfor"+targetClass.getName());Constructor<?extendsUnbinder>constructor=findBindingConstructorForClass(targetClass);if(constructor==null){returnUnbinder.EMPTY;}//noinspectionTryWithIdenticalCatchesResolvestoAPI19+onlytype.try{returnconstructor.newInstance(target,source);...}


那么就会走到xxx_ViewBinding的构造函数,那么就会初始化控件,同时也会设置监听。如下:

target.mEtUser=Utils.findRequiredViewAsType(source,R.id.et_user,"field'mEtUser'",EditText.class);target.mEtPwd=Utils.findRequiredViewAsType(source,R.id.et_pwd,"field'mEtPwd'",EditText.class);view=Utils.findRequiredView(source,R.id.btn_login,"method'btn_login'andmethod'btn_login_long'");view2131689593=view;view.setOnClickListener(newDebouncingOnClickListener(){@OverridepublicvoiddoClick(Viewp0){target.btn_login();}});view.setOnLongClickListener(newView.OnLongClickListener(){@OverridepublicbooleanonLongClick(Viewp0){returntarget.btn_login_long();}});



它的调用方式直接是target.mEtpwd,所以也就是说Activity的mEtpwd控件不能是private的,否则会引用不到。



参考网址:

butterknife github源代码下载


ButterKnife源代码解析


Java注解处理器分析


Eclipse中使用Java注解处理器


Android studio使用java注解处理器


JavaPoet介绍


调试Java注解处理器出错