前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >O3-开源框架使用之Butterknife 8.8.1及源码浅析

O3-开源框架使用之Butterknife 8.8.1及源码浅析

原创
作者头像
张风捷特烈
发布2018-09-11 10:26:47
4930
发布2018-09-11 10:26:47
举报
文章被收录于专栏:Android知识点总结
零、前言

我最喜欢的框架,没有之一:

编译期生成代码的方式,对运行时没有任何副作用。

加上AndroidStudio快捷键,简直好用之至。

添加依赖:
代码语言:txt
复制
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
混淆
代码语言:txt
复制
## butterknife start
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }

-keepclasseswithmembernames class * {
    @butterknife.* <fields>;
}

-keepclasseswithmembernames class * {
    @butterknife.* <methods>;
}
## butterknife end

一、Activity中使用
1.基础使用:
代码语言:txt
复制
public class MainActivity extends AppCompatActivity {

    @BindView(R.id.id_tv)
    TextView mIdTv;//绑定视图
    @BindView(R.id.id_btn)
    Button mIdBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //1.绑定Activity
        ButterKnife.bind(this);
    }

    @OnClick(R.id.id_btn)//单机事件
    public void onViewClicked() {
        Log.e(TAG, "onViewClicked: ");
    }

    @OnLongClick(R.id.id_btn)//长按事件
    public boolean onViewLongClicked() {
        Log.e(TAG, "onViewLongClicked: ");

        //和原生一样,返回true,抬起时不触发单机
        return true;
    }
}
多事件:
代码语言:txt
复制
@OnClick({R.id.id_btn, R.id.imageView})
public void onViewClicked(View view) {
    switch (view.getId()) {
        case R.id.id_btn:
            break;
        case R.id.imageView:
            break;
    }
}

二、Fragment中使用:

代码语言:txt
复制
public class MyFragment extends Fragment {

    @BindView(R.id.imageView)
    ImageView mImageView;
    Unbinder unbinder;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
    @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fg_test, container, false);
        //绑定View
        unbinder = ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        unbinder.unbind();
    }

    //销毁时解绑View
    @OnClick(R.id.imageView)
    public void onViewClicked() {
        Toast.makeText(getContext(), "hello", Toast.LENGTH_LONG).show();
    }
}

附:使用Fragment

代码语言:txt
复制
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.id_fl,new MyFragment());
ft.commit();

还有其他很多注解,感觉都没用太大用,下面看一下源码是怎么工作的


三、源码浅析:
1、首先来看这句话都进行了哪些事:ButterKnife.bind(this);
---B0:butterknife.ButterKnife#bind(android.app.Activity)

bind有6个重载的方法:这里使用的是一参Activity的bind方法

代码语言:txt
复制
  @NonNull @UiThread
  public static Unbinder bind(@NonNull Activity target) {
  //获取Activity对应窗口上的最顶端布局
    View sourceView = target.getWindow().getDecorView();
    //调用createBinding方法,见--B1
    return createBinding(target, sourceView);
  }
--B1:butterknife.ButterKnife#createBinding

这算一个非常核心的方法,6个bind()方法都是调用它

代码语言:txt
复制
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
    //获取target的class
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    //获取绑定Class的构造函数,见--B2
    Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
    //如果构造函数是空的,返回EMPTY的Unbinder枚举
    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
    //返回使用构造函数创建MainActivity_ViewBinding实例:
      return constructor.newInstance(target, source);
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
  }
--B2:butterknife.ButterKnife#findBindingConstructorForClass

通过字节码文件获取类的构造函数

代码语言:txt
复制
 @Nullable @CheckResult @UiThread
  private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
  
  //BINDINGS的声明:可见是一个LinkedHashMap,以class为键,构造函数为值。
  //static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();
    //从map中拿传入的cls的构造函数
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
    //如果不为空
    if (bindingCtor != null) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      //就返回拿到的构造函数
      return bindingCtor;
    }
    //否则,获取字节码文件的名字:如:com.toly1994.butterknifetest.MainActivity
    String clsName = cls.getName();
    //如果名字的字符串,是以android.或java.开头的
    if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      //返回空
      return null;
    }
    try {
    //加载com.toly1994.butterknifetest.MainActivity_ViewBinding类生成Clazz对象bindingClass:见:--B3
      Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
      //noinspection unchecked
      //获取自动生成的MainActivity_ViewBinding中的两参构造函数
      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);
    }
    //将cls和获取到的构造函数放入map
    BINDINGS.put(cls, bindingCtor);
    return bindingCtor;
  }
--B3:见鬼,哪来的什么MainActivity_ViewBinding让我加载?

Butter Knife会自动创建这个类,我们来看看它的庐山真面目

MainActivity_ViewBinding.png
MainActivity_ViewBinding.png

可见bind方法,主要是把XxxActivity创建一个XxxActivity_ViewBinding,并创建一个XxxActivity_ViewBinding对象


2、现在焦点就在MainActivity_ViewBinding的身上
com.toly1994.butterknifetest.MainActivity_ViewBinding
代码语言:txt
复制
// Generated code from Butter Knife. Do not modify!

public class MainActivity_ViewBinding implements Unbinder {
//持有一个MainActivity的引用
  private MainActivity target;
//持有一个View的引用
  private View view2131165244;

//一参构造:调用两参构造
  @UiThread
  public MainActivity_ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }
  
//两参构造:
  @UiThread
  public MainActivity_ViewBinding(final MainActivity target, View source) {
 //target赋值
    this.target = target;
    View view;
    //将target对象中的mIdTv赋值为:findRequiredViewAsType(视图,id,字段介绍,类名)方法:见--B4
    target.mIdTv = Utils.findRequiredViewAsType(source, R.id.id_tv, "field 'mIdTv'", TextView.class);
    //findRequiredView找到按钮,见:--B4-1
    view = Utils.findRequiredView(source, R.id.id_btn, "field 'mIdBtn' and method 'onViewClicked'");
    //view强转后为target对象中的mIdBtn赋值
    target.mIdBtn = Utils.castView(view, R.id.id_btn, "field 'mIdBtn'", Button.class);
    view2131165244 = view;
    //为按钮设置监听:见--B5
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
      //这里是调用的target也就是Activity中的onViewClicked方法
      //应该知道为什么可以简便的写点击事件了吧
        target.onViewClicked();
      }
    });
  }

  @Override
  @CallSuper
  //解绑:置空操作
  public void unbind() {
    MainActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;
    target.mIdTv = null;
    target.mIdBtn = null;
    view2131165244.setOnClickListener(null);
    view2131165244 = null;
  }
}
--B4:butterknife.internal.Utils#findRequiredViewAsType

根据类型查询需要的View

这个who只是在抛异常的时候告诉你,是谁异常

代码语言:txt
复制
  public static <T> T findRequiredViewAsType(View source, @IdRes int id, String who,
      Class<T> cls) {
     //findRequiredView(视图,id,字段介绍):见--B4-1
    View view = findRequiredView(source, id, who);
    //castView(视图,id,字段, Class):见--B4-2
    return castView(view, id, who, cls);
  }
--B4-1:findRequiredView(视图,id,字段介绍)

看到findViewById有没有小激动

代码语言:txt
复制
  public static View findRequiredView(View source, @IdRes int id, String who) {
  //真正的findViewById操作
    View view = source.findViewById(id);
    if (view != null) {
    //如果视图不为空就返回找到的视图
      return view;
    }
    //视图为空,就抛出一个IllegalStateException异常:
    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'"
    
--B4-2:castView(视图,id,字段, Class)
代码语言:txt
复制
public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
  try {
  //将View强转为T类型,T类型是Class<T>中的泛型,即findRequiredViewAsType中传入的类型
    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);
  }
}

cast()方法是Clazz的一个公共方法:由下可见它反会一个由传入值强转成的T类型对象

代码语言:txt
复制
    @SuppressWarnings("unchecked")
    public T cast(Object obj) {
        if (obj != null && !isInstance(obj))
            throw new ClassCastException(cannotCastMsg(obj));
        return (T) obj;
    }
--B5:这是butterknife源码中的一个类:

继承自:View.OnClickListener

代码语言:txt
复制
public abstract class DebouncingOnClickListener implements View.OnClickListener {
//是否可用
  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);
}

后记、
1.声明:

1本文由张风捷特烈原创,转载请注明

2欢迎广大编程爱好者共同交流

3个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正

4你的喜欢与支持将是我最大的动力

2.连接传送门:

更多安卓技术欢迎访问:安卓技术栈

我的github地址:欢迎star

简书首发,腾讯云+社区同步更新

张风捷特烈个人网站,编程笔记请访问:http://www.toly1994.com

3.联系我

QQ:1981462002

邮箱:1981462002@qq.com

微信:zdl1994328

4.欢迎关注我的微信公众号,最新精彩文章,及时送达:
公众号.jpg
公众号.jpg

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 零、前言
    • 添加依赖:
      • 混淆
      • 一、Activity中使用
        • 1.基础使用:
          • 多事件:
          • 三、源码浅析:
          • 1、首先来看这句话都进行了哪些事:ButterKnife.bind(this);
            • ---B0:butterknife.ButterKnife#bind(android.app.Activity)
              • --B1:butterknife.ButterKnife#createBinding
            • --B2:butterknife.ButterKnife#findBindingConstructorForClass
              • --B3:见鬼,哪来的什么MainActivity_ViewBinding让我加载?
              • 2、现在焦点就在MainActivity_ViewBinding的身上
                • com.toly1994.butterknifetest.MainActivity_ViewBinding
                  • --B4:butterknife.internal.Utils#findRequiredViewAsType
                    • --B4-1:findRequiredView(视图,id,字段介绍)
                    • --B4-2:castView(视图,id,字段, Class)
                  • --B5:这是butterknife源码中的一个类:
                  • 后记、
                    • 1.声明:
                      • 2.连接传送门:
                        • 3.联系我
                          • 4.欢迎关注我的微信公众号,最新精彩文章,及时送达:
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档