欢迎转载,转载请标明出处:

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框架(一)的更多相关文章

  1. Android开发之手把手教你写ButterKnife框架(三)

    欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52672188 本文出自:[余志强的博客] 一.概述 上一篇博客讲了, ...

  2. Android开发之手把手教你写ButterKnife框架(二)

    欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开 ...

  3. 手把手教你写DI_1_DI框架有什么?

    DI框架有什么? 在上一节:手把手教你写DI_0_DI是什么? 我们已经理解DI是什么 接下来我们就徒手撸一撸,玩个支持构造函数注入的DI出来 首先我们回顾一下 构造函数注入 的代码形式, 大概长这模 ...

  4. 手把手教你写DI_2_小白徒手撸构造函数注入

    小白徒手撸构造函数注入 在上一节:手把手教你写DI_1_DI框架有什么? 我们已经知道我们要撸哪些东西了 那么我们开始动工吧,这里呢,我们找小白同学来表演下 小白同学 :我们先定义一下我们的广告招聘纸 ...

  5. 手把手教你写DI_0_DI是什么?

    DI是什么? Dependency Injection 常常简称为:DI. 它是实现控制反转(Inversion of Control – IoC)的一个模式. fowler 大大大神 "几 ...

  6. 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接

    本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动. 1.引言 好多小白初次接触即时通讯(比如:IM或者消息推送应用)时,总是不 ...

  7. 只有20行Javascript代码!手把手教你写一个页面模板引擎

    http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...

  8. 手把手教你写Sublime中的Snippet

    手把手教你写Sublime中的Snippet Sublime Text号称最性感的编辑器, 并且越来越多人使用, 美观, 高效 关于如何使用Sublime text可以参考我的另一篇文章, 相信你会喜 ...

  9. 手把手教你写LKM rookit! 之 第一个lkm程序及模块隐藏(一)

    唉,一开始在纠结起个什么名字,感觉名字常常的很装逼,于是起了个这<手把手教你写LKM rookit> 我觉得: 你们觉得:...... 开始之前,我们先来理解一句话:一切的操作都是系统调用 ...

随机推荐

  1. SQL基础-----DDL

    1 (My)SQL入门 这里用了(My)SQL这样的标题,目的是介绍标准SQL的同时,也将MySQL在标准SQL上的扩展一同介绍给读者. 2:SQL分类 SQL语句主要可以划分为以下3个类别 .DDL ...

  2. 申请安装阿里云免费SSL证书

    微信小程序已经全面要求使用HTTPS服务了,还有苹果商店也是,所以,实现网站HTTPS已经很有必要.要实现HTTPS就需要一个SSL证书,证书大部分都很贵,不过也有一些免费的证书服务供个人开发者使用. ...

  3. C#之FTP上传下载(一)

    搭建FTP服务器 最近要实现这样一个功能:FTP服务器的上传和下载,搜集了一些资料,在c播客上看到昵称为"傻丫头和科技"的作者写的一篇文章写得挺好,有的地方个人觉得不是很详细,自己 ...

  4. NodeJS技巧

    1. 获取程序执行时间 // 打印程序执行时间 console.time() // some functions console.timeEnd() 2. 将函数异步化 setTimeout(() = ...

  5. 用js来实现那些数据结构08(链表02-双向链表)

    其实无论在任何语言中,一种数据结构往往会有很多的延伸和变种以应对不同场景的需要.其实前面我们所学过的栈和队列也是可以用链表来实现的.有兴趣的小伙伴可以自己尝试着去实现以下. 有点跑题了...,我们还是 ...

  6. js高阶函数应用—函数柯里化和反柯里化(二)

    第上一篇文章中我们介绍了函数柯里化,顺带提到了偏函数,接下来我们继续话题,进入今天的主题-函数的反柯里化. 在上一篇文章中柯里化函数你可能需要去敲许多代码,理解很多代码逻辑,不过这一节我们讨论的反科里 ...

  7. [HNOI 2016]大数

    Description 题库链接 给你一个长度为 \(n\) ,可含前导零的大数,以及一个质数 \(p\) . \(m\) 次询问,每次询问你一个大数的子区间 \([l,r]\) ,求出子区间中有多少 ...

  8. [Luogu 2090]数字对

    Description 对于一个数字对(a, b),我们可以通过一次操作将其变为新数字对(a+b, b)或(a, a+b). 给定一正整数n,问最少需要多少次操作可将数字对(1, 1)变为一个数字对, ...

  9. BZOJ 4372 烁烁的游戏

    Description 背景:烁烁很喜欢爬树,这吓坏了树上的皮皮鼠.题意:给定一颗n个节点的树,边权均为1,初始树上没有皮皮鼠.烁烁他每次会跳到一个节点u,把周围与他距离不超过d的节点各吸引出w只皮皮 ...

  10. [POJ 2104]K-th Number【模板】(主席树)

    题目背景 这是个非常经典的主席树入门题——静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输 ...