《ButterKnife源码分析.docx》由会员分享,可在线阅读,更多相关《ButterKnife源码分析.docx(13页珍藏版)》请在金锄头文库上搜索。
1、ButterKnife源码分析前言在N久之前,自从实验室里面的学长推荐我用butterknife后, 从此的项目再也离不开butterknife了,然而自以为对它很熟时,前不久今日头条实习生招聘二面却被面试官洗刷了一顿。然后整个二面完全是被虐的感觉,估计最后会挂,哎!当时被问到butterknife的实现,懵逼的我想都不想就答上了注解加反射。然而面试官却一脸疑问的问我:你确定?除了反射还有其他方法么?我了个去,难道butterknife不是用的反射?难道还有其他方法来实现这玩意儿么?不行,面试完了赶快clone 源码下来看看。不看不知道,一看吓一跳,原来还真不是用的注解加反射。在此感谢面试官为
2、我开启了新世界的大门,原来注解还能这么用!Butterknife用法我相信学过android开发应该基本上都用过Butterknife吧,就算没用过也听说过吧?毕竟是大名鼎鼎的Jake Wharton出品的东西。要是没用过的话可以看看这里,里面虽然是讲的Annotation,但是例子就是用注解加反射实现的低级的Butterknife。哈哈!用法里面大概也说了下。Butterknife原理讲到butterknife的原理。这里不得不提一下一般这种注入框架都是运行时注解,即声明注解的生命周期为RUNTIME,然后在运行的时候通过反射完成注入,这种方式虽然简单,但是这种方式多多少少会有性能的损耗。那
3、么有没有一种方法能解决这种性能的损耗呢? 没错,答案肯定是有的,那就是Butterknife用的APT(Annotation Processing Tool)编译时解析技术。APT大概就是你声明的注解的生命周期为CLASS,然后继承AbstractProcessor类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor的process方法,对注解进行处理,那么我们就可以在处理的时候,动态生成绑定事件或者控件的java代码,然后在运行的时候,直接调用bind方法完成绑定。其实这种方式的好处是我们不用再一遍一遍地写findViewByI
4、d和onClick了,这个框架在编译的时候帮我们自动生成了这些代码,然后在运行的时候调用就行了。源码解析上面讲了那么多,其实都不如直接解析源码来得直接,下面我们就一步一步来探究大神怎样实现Butterknife的吧。拿到源码的第一步是从我们调用的地方来突破,那我们就来看看程序里面是怎样调用它的呢? Override protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.
5、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 Jake Wharton); hello.setText(Say Hello); adapter = new SimpleAdapter(this); listOfThings.setAdapter(adapter)
6、; 上面是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 ViewB
7、inder 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()方法,那么问题来了,ViewBi
8、nder是个什么鬼?我们打开findViewBinderForClass()方法。 NonNull private static ViewBinder findViewBinderForClass(Class cls) throws IllegalAccessException, InstantiationException ViewBinder viewBinder = BINDERS.get(cls); if (viewBinder != null) return viewBinder; String clsName = cls.getName(); try Class viewBindi
9、ngClass = Class.forName(clsName + $ViewBinder); viewBinder = (ViewBinder) viewBindingClass.newInstance(); catch (ClassNotFoundException e) viewBinder = findViewBinderForClass(cls.getSuperclass(); BINDERS.put(cls, viewBinder); return viewBinder; 这里我去掉了一些Log信息,保留了关键代码,上面的BINDERS是一个保存了Class为key,Class$V
10、iewBinder为Value的一个LinkedHashMap,主要是做一下缓存,提高下次再来bind的性能。在第10行的时候,clsName 是我们传入要绑定的Activity类名,这里相当于拿到了Activity$ViewBinder这个东西,这个类又是什么玩意儿?其实从类名可以看出来,相当于Activity的一个内部类,这时候我们就要问了,我们在用的时候没有声明这个类啊?从哪里来的? 不要方,其实它就是我们在之前讲原理的时候说到的AbstractProcessor在编译的时候生成的一个类,我们后面再来看它,现在我们继续往下面分析。在第11行就用反射反射了一个viewBinder 实例出来
11、。刚刚说了,这个方法里面用linkhashMap做了下缓存,所以在15行的时候,就把刚刚反射的viewBinder作为value,Class作为key加入这个LinkedHashMap,下次再bind这个类的时候,就直接在第4行的时候取出来用,提升性能。现在返回刚刚的bind方法,我们拿到了这个Activity的viewBinder,然后调用它的bind方法。咦?这就完了?我们再点进viewBinder的bind方法看看。public interface ViewBinder Unbinder bind(Finder finder, T target, Object source);什么,接口
12、?什么鬼?刚刚不是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_
13、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,打
14、开看看:public class MainActivity$ViewBinder implements ButterKnife.ViewBinder 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(paramAnonymou