文档章节

ButterKnife的源码解析(2)

fyales
 fyales
发布于 2015/09/22 16:47
字数 1050
阅读 101
收藏 2

上一篇文章讲解了ButterKnife在编译阶段通过注解生成java文件,今天需要讲解的是ButterKnife绑定上下文。

一般的,绑定上下文的操作十分简单,在activity中通常是这样的:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
}

在Fragment中类似下面这样:

public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View view = inflater.inflate(R.layout.fragment_word_detail_display, container, false);
    ButterKnife.bind(this, view);
    return view;
}

在adapter里的ViewHolder里面使用:

static class ViewHolder {

    @Bind(R.id.item_icon)
    ImageView itemIcon;
    @Bind(R.id.note_label)
    TextView noteLabel;

    ViewHolder(View view) {
        ButterKnife.bind(this, view);
    }
}

从上文就可以看出,一般Butterknife通过bind方法绑定上下文,下面我们来阅读源码:

public static void bind(Activity target) {
		bind(target, target, Finder.ACTIVITY);
}

bind方法:

	static void bind(Object target, Object source, Finder finder) {
	Class<?> targetClass = target.getClass();
	try {
  	if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
  	ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
  	if (viewBinder != null) {
    	viewBinder.bind(finder, target, source);
  	}
	} catch (Exception e) {
  	throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
	}
}

在这个方法里面, 通过findViewBinderForClass(targetClass)获取与此activity相对应的我们通过注解在编译阶段生成java类的实例:

private static ViewBinder<Object> findViewBinderForClass(Class<?> cls)
    throws IllegalAccessException, InstantiationException {
  ViewBinder<Object> viewBinder = BINDERS.get(cls);
  if (viewBinder != null) {
    if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
    return viewBinder;
  }
  String clsName = cls.getName();
  if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
    if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
    return NOP_VIEW_BINDER;
  }
  try {
    Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
    //noinspection unchecked
    viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();
    if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
  } catch (ClassNotFoundException e) {
    if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
    viewBinder = findViewBinderForClass(cls.getSuperclass());
  }
  BINDERS.put(cls, viewBinder);
  return viewBinder;
}

这里的核心代码就是

     Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
    //noinspection unchecked
    viewBinder = (ViewBinder<Object>) viewBindingClass.newInstance();

在这里找到Activity对应的生成java文件的class,然后生成它的实例,最后将该实例以class为键,实例为值放进hashMap里,找到该实例后,那么接下来就应该开始绑定了,因为我们生成的class都实现了ViewBinder接口,所以bind方法会跑到具体实现类的方法里面去:

@Override public void bind(final Finder finder, final T target, Object source) {
  View view;
  view = finder.findRequiredView(source, 2131492943, "field 'textTv'");
  target.textTv = finder.castView(view, 2131492943, "field 'textTv'");
  view = finder.findRequiredView(source, 2131492944, "field 'clickBtn'");
  target.clickBtn = finder.castView(view, 2131492944, "field 'clickBtn'");
}

Finder是个Enum类,在这里我们传的是Finder.ACTIVITY,首先调用 finder.findRequiredView方法:

public <T> T findRequiredView(Object source, int id, String who) {
  T view = findOptionalView(source, id, who);
  if (view == null) {
    String name = getContext(source).getResources().getResourceEntryName(id);
    throw new IllegalStateException("Required view '"
        + name
        + "' with ID "
        + id
        + " for "
        + who
        + " was not found. If this view is optional add '@Nullable' annotation.");
  }
  return view;
}

public <T> T findOptionalView(Object source, int id, String who) {
  View view = findView(source, id);
  return castView(view, id, who);
}

@SuppressWarnings("unchecked") // That's the point.
public <T> T castView(View view, int id, String who) {
  try {
    return (T) view;
  } catch (ClassCastException e) {
    if (who == null) {
      throw new AssertionError();
    }
    String name = view.getResources().getResourceEntryName(id);
    throw new IllegalStateException("View '"
        + name
        + "' with ID "
        + id
        + " for "
        + who
        + " was of the wrong type. See cause for more info.", e);
  }
}

在findOptionalView方法里面调用的findView(source,id)最终会走到这里:

 ACTIVITY {
  @Override protected View findView(Object source, int id) {
    return ((Activity) source).findViewById(id);
  }

  @Override public Context getContext(Object source) {
    return (Activity) source;
  }
}

这串代码我们就很熟悉了,就是我们经常使用的findViewById()方法,通过这几个方法ButterKnife终于将我们需要绑定的控件转换成了一个View(没有具体到是什么种类的控件,例如textView,edittext什么的),具体到种类的控件是在第二个方法finder.castView方法实现的,还是以上面的例子说明,target.textTv是TextView,那么castView的返回值就是TextView的具体实例,这个的实现是通过强转实现。

一般的控件绑定就是这样,在这里,还有一个情况就是点击事件的绑定,其实这个也很简单。首先我们一般这样定义点击事件:

 @OnClick(R.id.text_tv)
void textTvClick(){
    Toast.makeText(this,"hello world",Toast.LENGTH_SHORT).show();
}

那么编译时生成的相应代码就是:

View view;
view = finder.findRequiredView(source, 2131492943, "field 'textTv' and method 'textTvClick'");
target.textTv = finder.castView(view, 2131492943, "field 'textTv'");
view.setOnClickListener(
  new butterknife.internal.DebouncingOnClickListener() {
    @Override public void doClick(
      android.view.View p0
    ) {
      target.textTvClick();
    }
  });

DebouningOnClickListener实现了View.OnClickListener的接口,其实现很简单,源代码如下:

public abstract class DebouncingOnClickListener implements View.OnClickListener {
  private static boolean enabled = true;

  private static final Runnable ENABLE_AGAIN = new Runnable() {
    @Override public void run() {
      enabled = true;
    }
  };

  @Override public final void onClick(View v) {
    if (enabled) {
      enabled = false;
      v.post(ENABLE_AGAIN);
      doClick(v);
    }
  }

  public abstract void doClick(View v);
}

到这里,ButterKnife源码就告一段落了,第一次剖析开源框架的源代码,写的有点粗糙,如果有什么不足的话,欢迎批评指正。

© 著作权归作者所有

fyales
粉丝 1
博文 18
码字总数 21081
作品 0
浦东
程序员
私信 提问
butterknife 10.1.0 核心源码分析

项目结构 项目依赖图: 如何使用: 1.先在项目根路径 build.gradle 里添加 classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0' 2.在app module build.gradle 里添加 dependenci......

萨x姆
03/12
120
0
Android组件化demo实现以及遇坑分享

首先贴出demo的github地址:GitHub - TenzLiu/TenzModuleDemo: android组件化demo 前言 前段时间看到最近一直很火的Android组件化然后就自己撸了一个demo,期间遇到了不少问题以及坑,在此记...

codeGoogle
2018/07/26
0
0
ButterKnife源码解析

ButterKnife(https://github.com/JakeWharton/butterknife)是一款android平台的依赖注入框架,通过该工具可以实现View、OnClickListener的注入,省去了findViewById、setOnClickListener的...

JasmineBen
2018/05/29
0
0
ButterKnife 牛油刀使用

一、butterknife介绍   ①官网 butterknife        ②Field and method binding for Android Views which uses annotation processing to generate boilerplate code for you   【......

小群子0618
2018/09/14
0
0
ButterKnife源码剖析

转载于:[http://blog.csdn.net/chenkai19920410/article/details/51020151] ButterKnife是Jake Wharton大神写开源框架。 项目托管地址: https://github.com/JakeWharton/butterknife。   ......

双鱼大猫
2016/12/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周一乱弹 —— 年迈渔夫遭黑帮袭抢

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @tom_tdhzz :#今日歌曲推荐# 分享Elvis Presley的单曲《White Christmas》: 《White Christmas》- Elvis Presley 手机党少年们想听歌,请使劲...

小小编辑
今天
1K
20
CentOS7.6中安装使用fcitx框架

内容目录 一、为什么要使用fcitx?二、安装fcitx框架三、安装搜狗输入法 一、为什么要使用fcitx? Gnome3桌面自带的输入法框架为ibus,而在使用ibus时会时不时出现卡顿无法输入的现象。 搜狗和...

技术训练营
昨天
5
0
《Designing.Data-Intensive.Applications》笔记 四

第九章 一致性与共识 分布式系统最重要的的抽象之一是共识(consensus):让所有的节点对某件事达成一致。 最终一致性(eventual consistency)只提供较弱的保证,需要探索更高的一致性保证(stro...

丰田破产标志
昨天
8
0
docker 使用mysql

1, 进入容器 比如 myslq1 里面进行操作 docker exec -it mysql1 /bin/bash 2. 退出 容器 交互: exit 3. mysql 启动在容器里面,并且 可以本地连接mysql docker run --name mysql1 --env MY...

之渊
昨天
16
0
python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部