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

mac2022-06-30  122

零、前言

我最喜欢的框架,没有之一: 编译期生成代码的方式,对运行时没有任何副作用。 加上AndroidStudio快捷键,简直好用之至。

添加依赖:
implementation 'com.jakewharton:butterknife:8.8.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
混淆
## butterknife start -keep class butterknife.** { *; } -dontwarn butterknife.internal.** -keep class **$$ViewBinder { *; } -keepclasseswithmembernames class * { @butterknife.* <fields>; } -keepclasseswithmembernames class * { @butterknife.* <methods>; } ## butterknife end

一、Activity中使用

1.基础使用:
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; } }
多事件:
@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中使用:

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

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方法

@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()方法都是调用它

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

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

@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

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


2、现在焦点就在MainActivity_ViewBinding的身上

com.toly1994.butterknifetest.MainActivity_ViewBinding
// 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只是在抛异常的时候告诉你,是谁异常

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有没有小激动

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)
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类型对象

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

继承自:View.OnClickListener

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

转载于:https://www.cnblogs.com/toly-top/p/9781872.html

相关资源:butterknife 8.8.1
最新回复(0)