零、前言
我最喜欢的框架,没有之一:
编译期生成代码的方式,对运行时没有任何副作用。
加上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让我加载?
MainActivity_ViewBinding.pngButter Knife会自动创建这个类,我们来看看它的庐山真面目
可见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
网友评论