Android开发之手把手教你写ButterKnife框架(一)
欢迎转载,转载请标明出处:
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;
@Override
protected 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);
}
}
初始化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);
//初始化Views
ButterKnife.bind(this);
}
}
相比之下,极大的简化了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 @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
获取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
}
}
createBinding方法的第一个参数target就是我们的activity实例,source就是decorView。上面的代码也比较简单,我也加上了注释。这个方法我就不多说了。然后看看findBindingConstructorForClass方法是怎么实现的:
@Nullable @CheckResult @UiThread
private 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里的类则直接return
if (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;
}
findBindingConstructorForClass方法核心代码是下面2行代码:
Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
意思就是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
@UiThread
public 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
}
}
target其实是我们上面的activity,source就是DecorView。发现所有的View的初始化工作全部放在了SimpleActivity_ViewBinding构造方法里。
// Generated code from Butter Knife. Do not modify!
通过这句话我们知道,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的初始化工作。
Android开发之手把手教你写ButterKnife框架(一)的更多相关文章
- Android开发之手把手教你写ButterKnife框架(三)
欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52672188 本文出自:[余志强的博客] 一.概述 上一篇博客讲了, ...
- Android开发之手把手教你写ButterKnife框架(二)
欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开 ...
- 手把手教你写DI_1_DI框架有什么?
DI框架有什么? 在上一节:手把手教你写DI_0_DI是什么? 我们已经理解DI是什么 接下来我们就徒手撸一撸,玩个支持构造函数注入的DI出来 首先我们回顾一下 构造函数注入 的代码形式, 大概长这模 ...
- 手把手教你写DI_2_小白徒手撸构造函数注入
小白徒手撸构造函数注入 在上一节:手把手教你写DI_1_DI框架有什么? 我们已经知道我们要撸哪些东西了 那么我们开始动工吧,这里呢,我们找小白同学来表演下 小白同学 :我们先定义一下我们的广告招聘纸 ...
- 手把手教你写DI_0_DI是什么?
DI是什么? Dependency Injection 常常简称为:DI. 它是实现控制反转(Inversion of Control – IoC)的一个模式. fowler 大大大神 "几 ...
- 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接
本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.引言 好多小白初次接触即时通讯(比如:IM或者消息推送应用)时,总是不 ...
- 只有20行Javascript代码!手把手教你写一个页面模板引擎
http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...
- 手把手教你写Sublime中的Snippet
手把手教你写Sublime中的Snippet Sublime Text号称最性感的编辑器, 并且越来越多人使用, 美观, 高效 关于如何使用Sublime text可以参考我的另一篇文章, 相信你会喜 ...
- 手把手教你写LKM rookit! 之 第一个lkm程序及模块隐藏(一)
唉,一开始在纠结起个什么名字,感觉名字常常的很装逼,于是起了个这<手把手教你写LKM rookit> 我觉得: 你们觉得:...... 开始之前,我们先来理解一句话:一切的操作都是系统调用 ...
随机推荐
- 线程的同步控制(Synchronization)
临界区(Critical Sections) 摘要 临界区(Critical Section) 用来实现"排他性占有".适合范围时单一进程的各线程之间. 特点 一个局部对象,不是一 ...
- tkinter 创建登陆注册界面
import tkinter as tk from tkinter import messagebox #设置窗口居中 def window_info(): ws = window.winfo_scr ...
- pyqt5 动画学习(四) 旋转动画,使用QGraphicsView让自己的控件旋转起来
今天学有所成,赶紧记下今天的成果 之前三篇文章分别演示了空间的大小改变,移动,及颜色变化.在后续研究旋转的过程中即为艰难 如果你是使用pyqt4,那么使用QGraphicsItemAnimation便 ...
- [USACO 07NOV]Cow Relays
Description For their physical fitness program, N (2 ≤ N ≤ 1,000,000) cows have decided to run a rel ...
- [NOIp 2016]天天爱跑步
Description 小C同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成打卡任务. 这个游戏的地图 ...
- 51nod 1364 最大字典序排列(线段树)
1364 最大字典序排列基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 给出一个1至N的排列,允许你做不超过K次操作,每次操作可以将相邻的两个数交换,问能够得到的字 ...
- [51nod1238]最小公倍数之和V3
来自FallDream的博客,未经允许,请勿转载,谢谢. ----------------------------------------------------------------------- ...
- 关于 Cannot forward after response has been committed的错误
这个错误顾名思义:就是在给客户端做出提交响应完毕后,就不能再次使用request的转发. 代码原先是这样的: package com.bdqn.jsp.study.web.filter; import ...
- 两个文件比较之comm命令
comm命令可用于两个文件之间的比较.它有很多不错的选项可用来调整输出,以便我们执行交集.求差(difference)以及差集操作. 交集:打印出两个文件所共有的行. 求差:打印出指定文件所包含的 ...
- Spring学习笔记5——注解方式AOP
第一步:注解配置业务类 使用@Component("Pservice")注解ProductService 类 package com.spring.service; import ...