http://blog.csdn.net/johnny901114/article/details/52662376

一、概述

JakeWharton我想在Android界无人不知,无人不晓的吧, ButterKnife这个框架就是出自他只手。这个框架我相信很多人都用过,本系列博客就是带大家更加深入的认识这个框架,ButterKnife截至目前已有1w+的star:

如果我们对于这个优秀框架还是停留在使用阶段,那就太可惜。

本系列文章的主要内容如下: 
1,ButterKnife是什么? 
2,ButterKnife的作用和功能介绍。 
3,ButterKnife的实现原理。 
4,自己动手实现个ButterKnife。

二、ButterKnife是什么?

ButterKnife是一个编译时的依赖注入框架(compile-time dependency injection framework)用来简化android中类似findViewById、setOnclickListener等的模板代码。 
比如在写activity界面的时候常常有如下代码:

public class MyActivity extents Activity{private EditText etConsultValidDate;private TextView tvToolbarCenter;private TextView  tvLeftAction;private TextView  tvRightAction;private TextView tvConsultTip;private TextView tvSuggestTime;private EditText etConsultTitle;private EditText etConsultDesc;private EditText etConsultTime;private EditText etConsultNumber;private RelativeLayout rlContactInfo;private LinearLayout llAnswerTime;private TextView tvAnswerTime, tvAnswerTimePre;private TextView tvToolbarRight;private LinearLayout llBottom;private int from;private RelativeLayout rlOppositeInfo;private ImageView ivHeadIcon;private TextView tvOppositeUsername, tvOppositeDesc;@Overrideprotected void initViews() {tvExpertIdentify = (TextView) findViewById(R.id.tv_expert_identify);llBottom = (LinearLayout) findViewById(R.id.ll_bottom);rlOppositeInfo = (RelativeLayout) findViewById(R.id.rl_opposite_info);ivHeadIcon = (ImageView) findViewById(R.id.iv_head_icon);tvOppositeUsername = (TextView) findViewById(R.id.tv_opposite_username);tvOppositeDesc = (TextView) findViewById(R.id.tv_opposite_desc);rbOppositeScore = (RatingBar) findViewById(R.id.rbar_star);tvUserCompany = (TextView) findViewById(R.id.tv_user_company);infoArrow = findViewById(R.id.iv_member_info_arrow);tvConsultTip = (TextView) findViewById(R.id.tv_consult_tip);tvLeftAction = (TextView) findViewById(R.id.tv_left_action);tvRightAction = (TextView) findViewById(R.id.tv_right_action);llAnswerTime = (LinearLayout) findViewById(R.id.ll_answer_time);tvAnswerTimePre = (TextView) findViewById(R.id.tv_answer_time_pre);tvAnswerTime = (TextView) findViewById(R.id.tv_answer_time);tvLeftAction.setOnClickListener(this);tvRightAction.setOnClickListener(this);etConsultTitle = (EditText) findViewById(R.id.et_consult_title);etConsultDesc = (EditText) findViewById(R.id.et_consult_desc);etConsultTime = (EditText) findViewById(R.id.et_contact_time);etConsultNumber = (EditText) findViewById(R.id.et_contact_number);etConsultValidDate = (EditText) findViewById(R.id.et_consult_valid_day);tvSuggestTime = (TextView) findViewById(R.id.tv_contact_time);}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

初始化Views大量的没有技术含量的模板代码。如果界面比较复杂的话,这样的代码变得更多。使用ButterKnife可以很好的简化上面冗长的代码。

public class MyActivity extents Activity{@BindView(R.id.xxx) EditText etConsultValidDate;@BindView(R.id.xxx) TextView tvToolbarCenter;@BindView(R.id.xxx) TextView tvLeftAction;@BindView(R.id.xxx) TextView tvRightAction;@BindView(R.id.xxx) TextView tvConsultTip;@BindView(R.id.xxx) TextView tvSuggestTime;@BindView(R.id.xxx) EditText etConsultTitle;@BindView(R.id.xxx) EditText etConsultDesc;@BindView(R.id.xxx) EditText etConsultTime;@BindView(R.id.xxx) EditText etConsultNumber;@BindView(R.id.xxx) RelativeLayout rlContactInfo;@BindView(R.id.xxx) LinearLayout llAnswerTime;@BindView(R.id.xxx) TextView tvAnswerTime, tvAnswerTimePre;@BindView(R.id.xxx) TextView tvToolbarRight;@BindView(R.id.xxx) LinearLayout llBottom;@BindView(R.id.xxx) RelativeLayout rlOppositeInfo;@BindView(R.id.xxx) ImageView ivHeadIcon;@BindView(R.id.xxx) TextView tvOppositeUsername;@BindView(R.id.xxx) TextView tvOppositeDesc;@Override public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.simple_activity);//初始化ViewsButterKnife.bind(this); }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

相比之下,极大的简化了View的初始化代码。

三、ButterKnife的功能介绍(所有的功能)

除了上面的@BindView注解,还其他功能:

1. 使用@BindViews初始化多个View

@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name }) 
List nameViews;

2. 使用@OnClick设置监听事件

@OnClick(R.id.submit) 
public void submit(View view) { 
// TODO do something… 
}

如果不想要submit方法参数可以去掉如: 
@OnClick(R.id.submit) 
public void submit() { 
// TODO do something… 
}

View的参数还可以自动转换,比如给TextView设置点击事件 
@OnClick(R.id.submit) 
public void submit(TextView textView) { 
// TODO do something… 
}

如果是自定义的View可以不指定View Id 如: 
public class FancyButton extends Button { 
@OnClick 
public void onClick() { 
// TODO do something! 

}

3. listView item点击事件

@OnItemSelected(R.id.list_view) 
void onItemSelected(int position) { 
// TODO … 
}

4. view的onTouchEvent

@OnTouch(R.id.example) boolean onTouch() { 
Toast.makeText(this, “Touched!”, Toast.LENGTH_SHORT).show(); 
return false; 
}

5. 监听EditText的addTextChangedListener

@OnTextChanged(R.id.example) void onTextChanged(CharSequence text) { 
Toast.makeText(this, “Text changed: ” + text, Toast.LENGTH_SHORT).show(); 
}

6. 设置ViewPager的OnPageChangeListener

@OnPageChange(R.id.example_pager) void onPageSelected(int position) { 
Toast.makeText(this, “Selected ” + position + “!”, Toast.LENGTH_SHORT).show(); 
}

7. 设置TextView的OnEditorActionListener(该事件主要用来设置软键盘上的按钮)

@OnEditorAction(R.id.example) boolean onEditorAction(KeyEvent key) { 
Toast.makeText(this, “Pressed: ” + key, Toast.LENGTH_SHORT).show(); 
return true; 
}

8. 设置View的OnFocusChangeListener事件

@OnFocusChange(R.id.example) void onFocusChanged(boolean focused) { 
Toast.makeText(this, focused ? “Gained focus” : “Lost focus”, Toast.LENGTH_SHORT).show(); 
}

9. 设置View的OnLongClickListener长按事件

@OnLongClick(R.id.example) boolean onLongClick() { 
Toast.makeText(this, “Long clicked!”, Toast.LENGTH_SHORT).show(); 
return true; 
}

10. 关于资源的绑定

@BindString(R.string.title) String title; //字符串 
@BindDrawable(R.drawable.graphic) Drawable graphic; //drawable 
@BindColor(R.color.red) int red; // int or ColorStateList field 
@BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value) field 
@BindArray(R.array.countries) String[] countries; 字符串数组 
@BindArray(R.array.icons) TypedArray icons; 
@BindBool(R.bool.is_tablet) boolean isTablet;

四、ButterKnife的实现原理

通过上面的例子我们知道,要使用ButterKnife首先要在目标代码使用注解,然后在onCreate生命周期方法里调用ButterKnife.bind(this);方法。使用注解没什么好说的,那只有看看ButterKnife.bind(this);这个方法是怎么实现的:

  @NonNull @UiThreadpublic static Unbinder bind(@NonNull Activity target) {View sourceView = target.getWindow().getDecorView();return createBinding(target, sourceView);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

获取activity的decorView,然后调用createBinding方法:

private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {//获取target的字节码Class<?> targetClass = target.getClass();if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());//搜索构造方法(什么类的构造方法,下面会分析)Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);if (constructor == null) {return Unbinder.EMPTY;}//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.try {//通过反射构造类实例return constructor.newInstance(target, source);} catch (IllegalAccessException e) {//Ignore Exceptions} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

createBinding方法的第一个参数target就是我们的activity实例,source就是decorView。上面的代码也比较简单,我也加上了注释。这个方法我就不多说了。然后看看findBindingConstructorForClass方法是怎么实现的:

  @Nullable @CheckResult @UiThreadprivate static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {//从容器中查找构造方法,如果找到了直接返回。Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);if (bindingCtor != null) {if (debug) Log.d(TAG, "HIT: Cached in binding map.");return bindingCtor;}String clsName = cls.getName();//如果是android framework里的类则直接returnif (clsName.startsWith("android.") || clsName.startsWith("java.")) {if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");return null;}try {//拼接类名,然后获取该类的字节码Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");//noinspection unchecked//获取该类的构造方法bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");} catch (ClassNotFoundException e) {if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());bindingCtor = findBindingConstructorForClass(cls.getSuperclass());} catch (NoSuchMethodException e) {throw new RuntimeException("Unable to find binding constructor for " + clsName, e);}BINDINGS.put(cls, bindingCtor);return bindingCtor;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

findBindingConstructorForClass方法核心代码是下面2行代码:

Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
  • 1
  • 2
  • 1
  • 2

意思就是target的类型(qualified name)拼接_ViewBinding,然后通过获取拼接后类的构造方法。那么clsName + _ViewBinding 这个类是从哪里来的。 
我们把butterKnife的源码(8.4.0版本)下载下来,build代码后,查找以_ViewBinding为结尾的Java类,发现有十个只有,都位于各自所在项目的build->gernerated->source->apt->debug目录下。 
以里面的SimpleActivity_ViewBinding为例:

// Generated code from Butter Knife. Do not modify!
package com.example.butterknife.library;
public class SimpleActivity_ViewBinding<T extends SimpleActivity> implements Unbinder {//ignore some code@UiThreadpublic SimpleActivity_ViewBinding(final T target, View source) {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', and method 'sayGetOffMe'");target.hello = Utils.castView(view, R.id.hello, "field 'hello'", Button.class);//ignore some code}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

target其实是我们上面的activity,source就是DecorView。发现所有的View的初始化工作全部放在了SimpleActivity_ViewBinding构造方法里。

// Generated code from Butter Knife. Do not modify!
  • 1
  • 1

通过这句话我们知道,SimpleActivity_ViewBinding是ButterKnife生成的。那么ButterKnife是怎么生成这个类的呢?

通过一个叫APT(Annotation Processing Tool)工具生成对应的类。

总结下: 
1. butterKnife是一个运行时依赖注入框架,有效地帮我们简化一些重复代码。 
2. butterKnife在ButterKnife.bind方法里通过反射调用对应的类构造方法执行初始化工作,所以butterKnife并不是完全没有使用反射,只在这个地方用到了。所以butterKnife的效率也是很高的。对于反射这个技术,不应该持极端态度(彻底不用,或到处滥用)。特别是在android中,到处都是反射,对性能也是有一定的影响的。 
3. butterknife使用 apt技术来生成java类。

本系列预计有三篇文章,下一篇文章将介绍在android studio中如何使用apt。最后一篇文章讲如何实现调用bind方法就完成view的初始化工作。

Butter Knife 详细使用相关推荐

  1. Android——教你10分钟手敲 Butter Knife(小刀注解)

    教你10分钟手敲 Butter Knife(小刀注解) 在用 AndroidStudio 集成 Butter Knife(小刀注解)的时候感觉像极了J2EE的Spring IOC容器 自己研究了一下, ...

  2. android Butter Knife 使用详解

    Butter Knife github连接:https://github.com/JakeWharton/butterknife 本文使用的butterknife版本7.0.1 butterknife ...

  3. Butter Knife:一个安卓视图注入框架

    Butter Knife:一个安卓视图注入框架 2014年5月8日 星期四 14:52 官网: http://jakewharton.github.io/butterknife/ GitHub地址: ...

  4. Android Studio Butter Knife —— 快速开发

    Butter Knife是一个Android的注解框架,可以帮助用户快速完成视图.资源与对象的绑定,完成事件的监听.(也就是少写findViewById()) 具体的介绍可以参考官方主页: http: ...

  5. Android RoboGuice开源框架、Butter Knife开源框架浅析

    Google Guice on Android(RoboGuice) 今天介绍一下Google的这个开源框架RoboGuice, 它的作用跟之前讲过的Dagger框架差点儿是一样的,仅仅是Dagger ...

  6. [轉]Android Libraries 介紹 - Butter knife

    原文地址 Butter Knife 簡介 Butter Knife - Field and method binding for Android views.助你簡化程式碼,方便閱讀. 使用方法 開發 ...

  7. html中添加butter按钮,Butter Knife使用详解

    Butter Knife 官方说明给出的解释是 Bind Android views and callbacks to fields and methods. Field and method bin ...

  8. Butter Knife

    Butter Knife,专门为Android View设计的绑定注解,专业解决各种findViewById. 简介 对一个成员变量使用@BindView注解,并传入一个View ID, Butter ...

  9. Butter Knife(牛油刀)

    官网(Butter Knife(牛油刀)) github 添加依赖 apply plugin: 'com.jakewharton.butterknife' android { - // Butterk ...

最新文章

  1. 分类问题-样本权重(sample_weight)和类别权重(class_weight)
  2. React实践debug:JSX输出的限制(存疑)
  3. SD-SD用到的文本对象列表
  4. pycharm如何调试函数中的断点?找出操作对应的代码部分
  5. Linux下WPS自主设置快捷键,电脑wps的word怎么自主设定快捷键
  6. 漫步数理统计十——连续随机变量(上)
  7. IO多路复用之select、poll、epoll详解
  8. 【岗位详情】腾讯广告大数据开发工程师(北京)
  9. 最好的网页浏览器_如何实现整个网页的完整截图
  10. SVN创建项目以及检出项目
  11. Linux下硬盘分区的最佳方案
  12. 使用微信模板消息时,使发送的文本消息换行展示
  13. 常见的DoS攻击防御方式
  14. 【openGL2021版】天空盒
  15. zz 主要分类方法介绍
  16. Spark 名词解释
  17. c语言程序商品的打折,C语言程序设计习题doc.doc
  18. 《论文写作》课程感想
  19. C++20新特性—概述
  20. vue中使用i18n

热门文章

  1. 如何在opencv 和 vs 2019 调整运行窗口的大小
  2. Effective C++ 条款02:尽量使用const,enum,inline替换#define
  3. 如何调用浏览器的拾色器
  4. 机器学习 | 从文本分析看《解忧杂货店》
  5. 静静的活不埋怨也不嘲笑
  6. 什么是ETF量化接口?
  7. Linux就该这么学--第五期 学习笔记
  8. 华为云云容器引擎CCE踩坑记
  9. 职业梦想是计算机的英语作文,我梦想的职业高中英语作文
  10. 《Java小子怒闯数据结构九重天》第二重天——字符串