ButterKnife 菜鸟入门手册

简单使用

一 导入配置

  • 在gradle中导入依赖

      dependencies {
     implementation 'com.jakewharton:butterknife:10.1.0'
     annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
    

二 基本使用

  • Activity

    public class MainActivity extends AppCompatActivity {
     @BindView(R.id.tv_title)
     TextView title;
    ​
     @OnClick(R.id.bt_submit)
     public void submit() {
     title.setText("hello world");
     }
    ​
     private Unbinder unbinder;
    ​
     @Override
     protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_main);
     unbinder = ButterKnife.bind(this);
     }
     @Override
     protected void onDestroy() {
     unbinder.unbind();
     super.onDestroy();
     }
     }
    
  • Fragment

    public class SimpleFragment extends Fragment {
    
     @BindView(R.id.fragment_text_view)
     TextView mTextView;
     private Unbinder unbinder;
    
     public SimpleFragment() {
     }
    
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     Bundle savedInstanceState) {
     View view = inflater.inflate(R.layout.fragment_simple, container, false);
     unbinder = ButterKnife.bind(this, view);
     mTextView.setText("TextView in Fragment are found!");
     return view;
     }
     @Override
     protected void onDestroy() {
     unbinder.unbind();
     super.onDestroy();
         }
    }
    
    
  • Adapter

   static class ViewHolder {
    @BindView(R.id.person_name)
    TextView name;
    @BindView(R.id.person_age)
    TextView age;
    @BindView(R.id.person_location)
    TextView location;
    @BindView(R.id.person_work)
    TextView work;

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

三 绑定源码解析

最后编译一下项目。直觉告诉我们应该从ButterKnife.bind(this)开始分析,因为它像是 ButterKnife 和 Activity 建立绑定关系的过程,看具体的代码:

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
 View sourceView = target.getWindow().getDecorView();
 return createBinding(target, sourceView);
}

sourceView代表当前界面的顶级父 View,是一个FrameLayout,继续看createBinding()方法:

 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);
 } 
 ...
 }

首先得到要绑定的 Activity 对应的 Class,然后用根据 Class 得到一个继承了UnbinderConstructor,最后通过反射constructor.newInstance(target, source)得到Unbinder子类的一个实例,到此ButterKnife.bind(this)操作结束。这里我们重点关注findBindingConstructorForClass()方法是如何得到constructor实例的:

@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();
 if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
 if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
 return null;
 }
 try {
 Class<?> bindingClass = cls.getClassLoader().loadClass(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;
 }

整体的流程是先检查BINDINGS是否存在 Class 对应的 Constructor,如果存在则直接返回,否则去构造对应的 Constructor。其中BINDINGS是一个LinkedHashMapMap<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>(),缓存了对应的 Class 和 Constructor 以提高效率! 接下来看当不存在对应 Constructor 时如何构造一个新的,首先如果clsName是系统相关的,则直接返回 null,否则先创建一个新的 Class:

Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");

这里新的 Class 的 name 是 com.shh.sometest.MainActivity_ViewBinding,最后用新的 Class 创建一个 继承了Unbinder的 Constructor,并添加到BINDINGS

bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
BINDINGS.put(cls, bindingCtor);

所以最终bind()方法返回的是MainActivity_ViewBinding类的实例。既然可以返回MainActivity_ViewBinding的实例,那MainActivity_ViewBinding这个类肯定是存在的。可以在如下目录找到它(这个类是在项目编译时期由 annotationProcessor 生成的)

MainActivity_ViewBinding

来看看它里边都做了那些事:

  public class MainActivity_ViewBinding implements Unbinder {
 private MainActivity target;
​
 private View view2131165217;
​
 @UiThread
 public MainActivity_ViewBinding(MainActivity target) {
 this(target, target.getWindow().getDecorView());
 }
​
 @UiThread
 public MainActivity_ViewBinding(final MainActivity target, View source) {
 this.target = target;
​
 View view;
 target.title = Utils.findRequiredViewAsType(source, R.id.tv_title, "field 'title'", TextView.class);
 view = Utils.findRequiredView(source, R.id.bt_submit, "method 'submit'");
 view2131165217 = view;
 view.setOnClickListener(new DebouncingOnClickListener() {
 @Override
 public void doClick(View p0) {
 target.submit();
 }
 });
 }
​
 @Override
 @CallSuper
 public void unbind() {
 MainActivity target = this.target;
 if (target == null) throw new IllegalStateException("Bindings already cleared.");
 this.target = null;
​
 target.title = null;
​
 view2131165217.setOnClickListener(null);
 view2131165217 = null;
 }
}

之前createBinding()方法中return constructor.newInstance(target, source);操作使用的就是MainActivity_ViewBinding类两个参数的构造函数。

重点看这个构造函数,首先是给target.title赋值,即MainActivity中的TextView,用到了一个findRequiredViewAsType()方法:

public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
 Class<T> cls) {
 View view = findRequiredView(source, id, who);
 return castView(view, id, who, cls);
 }

继续看findRequiredView()方法:

public static View findRequiredView(View source, @IdRes int id, String who) {
 View view = source.findViewById(id);
 if (view != null) {
 return view;
 }
 String name = getResourceEntryName(source, id);
 throw new IllegalStateException("Required view '"
 + name
 + "' with ID "
 + id
 + " for "
 + who
 + " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
 + " (methods) annotation.");
 }

最终还是通过findViewById()得到对应View,然后就是castView()

public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
 try {
 return cls.cast(view);
 } catch (ClassCastException e) {
 String name = getResourceEntryName(view, id);
 throw new IllegalStateException("View '"
 + name
 + "' with ID "
 + id
 + " for "
 + who
 + " was of the wrong type. See cause for more info.", e);
 }
 }

核心就是把findRequiredView()得到的 View 转成指定类型的 View ,如果 xml 中定义的 View 和 Activity 中通过注解绑定的 View 类型不一致,就会抛出上边方法的异常,可能很多人都遇到过。这样target.title的赋值就结束了,接下来就是直接使用findRequiredView()找到对应 id 的Button,不用进行类型转换,然后给它绑定点击事件,最终调用了在MainActivity中给Button绑定点击事件时定义的submit()方法。到这里就完成了 相关 View 的赋值以及事件绑定!

MainActivity_ViewBinding类中还有一个unbind()方法,需要在ActivityFragmentonDestory()中调用,以完成 相关 View 引用的释放以及点击事件的解绑操作!

四 使用中可能出现的细节问题

  1. 设置view时候不要用private
     @BindView(R.id.fra_pro_username)
     TextView usernameTextView; 
     //正确
     @BindView(R.id.fra_pro_bio)
     Private TextView bioTextView;
     //错误  实际绑定view是在其他辅助类中调用findviewbyid绑定的,如果设为private辅助类就没有权限更改
  1. 不要漏了ButterKnife.bind(this) 还有和setContentView(R.layout.activity_profile)顺序要正确,并且activity和fragment和viewholder之中绑定函数的参数有点不同

      setContentView(R.layout.activity_profile);
     ButterKnife.bind(this);
     //正确
    
     ButterKnife.bind(this);
     setContentView(R.layout.activity_profile);
     //错误
    
    
  2. 有些findviewbyid是不能改写的

    accountTextView = getActivity().findViewById(R.id.user_name);
    joinTimeTextView = getActivity().findViewById(R.id.user_jointime);
    locationTextView = getActivity().findViewById(R.id.user_location);
    ​
    //这种不能改写,因为是在fragmnet中调用的getavtivity()得到活动后在调用的findviewbyid
    而butterknife 绑定的view是fragment的所以不能改写
    ​
    View view = inflater.inflate(R.layout.fragment_profile_info, container, false);
    userAvatarImageView = view.findViewById(R.id.user_avatar);
    //可以改写,理由如上
    ​
    totalAccountBtn = navigationView.getHeaderView(0).findViewById(R.id.total_account_bn);
    //不能改写,理由如上,原因butterknife绑定的view和调用findviewbyId的view不同</pre>

五 使用中可能出现的细节问题

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容