1.使用ButterKnife首先在项目的build添加
implementation 'com.jakewharton:butterknife:8.4.0'
就ok了。
2.我们直接上代码吧
public class SideslipDeletActivity extends AppCompatActivity {
@BindView(R.id.my_recyclerView)
RecyclerView my_recyclerView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sideslip_delet);
ButterKnife.bind(this);
}
@OnClick(R.id.btn_tv)
public void btn_tv() {
Toast.makeText(this,"show",Toast.LENGTH_SHORT).show();
}
}
点击 ButterKnife.bind(this);我们看到
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
我们可以看到通过target.getWindow().getDecorView()根据activity得到DecorView,然后在传递给createBinding。
private static Unbinder createBinding(@NonNull Object target, @NonNull View source) {
//拿到SideslipDeletActivity .class
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
//通过targetClass获取一个构造器
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
//使用构造器构造一个实例,参数是target(SideslipDeletActivity )和source(SideslipDeletActivity 对应的根视图)
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);
}
}
createBinding()方法主要就是拿到我们绑定的Activity的Class,然后通过Constructor构造器获取一个Unbinder子类的构造方法,然后在调用newInstance(target, source)通过构造方法获取到Unbinder子类的一个实例,这里传入两个参数,说明构造方法里需要两个参数。我们打开findBindingConstructorForClass()方法。
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//首先从BINDINGS获取Constructor,BINDINGS可以理解为缓存,已经用过的Constructor都会记录在BINDINGS中
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
//如果从BINDINGS找到了Constructor直接返回
if (bindingCtor != null) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
//通过类名如果发现是android类文件或者java类文件,则返回空。这里可能奇怪为什么会遇到android或者java文件?我们带着疑问继续看
if (clsName.startsWith("android.") || clsName.startsWith("java.")) {
if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
return null;
}
try {
//通过反射获取到名叫SideslipDeletActivity_ViewBinding的类的构造器
Class<?> bindingClass = Class.forName(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());
//如果SideslipDeletActivity_ViewBinding这个类没有找到就获取SideslipDeletActivity.class的父类,继续执行 findBindingConstructorForClass,这里是个递归,这个递归什么时候停止呢?直到遇到android或者java类文件。
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
//最后将这个Constructor放到缓存中,这样再次用到MainActivity_ViewBinding时就可以不用反射,可以提高运行效率
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
findBindingConstructorForClass方法是一个比较核心的方法
这个方法是利用SideslipDeletActivity.class类名,找到名叫SideslipDeletActivity_ViewBinding的类并创建一个实例。
其实经过ButterKnife的努力将语句ButterKnife.bind(this)替换成了new SideslipDeletActivity_ViewBinding(this, getWindow().getDecorView())
现在我们来看一下SideslipDeletActivity_ViewBinding
public class SideslipDeletActivity_ViewBinding<T extends SideslipDeletActivity> implements Unbinder {
protected T target;
@UiThread
public SideslipDeletActivity_ViewBinding(T target, View source) {
this.target = target;
target.my_recyclerView = Utils.findRequiredViewAsType(source, R.id.my_recyclerView, "field 'my_recyclerView'", RecyclerView.class);
}
@Override
@CallSuper
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.my_recyclerView = null;
this.target = null;
}
}
看一下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);
}
}
这个方法的作用是类型的强制转换。
经过ButterKnife再一次的不懈努力,我们成功的将
@BindView(R.id.my_recyclerView)
RecyclerView my_recyclerView;
转换成了:
my_recyclerView= (TextView) getWindow().getDecorView().findViewById(R.id.my_recyclerView;
@onClick是如何转换的
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
/**
* 侧滑删除
*/
@OnClick(R.id.btn_sideslip)
public void btn_sideslip() {
Navigate.startSideslipDeletActivity(MainActivity.this);
}
}
我们来看一下对应MainActivity_ViewBinding
public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
protected T target;
private View view2131165218;
@UiThread
public MainActivity_ViewBinding(final T target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.btn_sideslip, "method 'btn_sideslip'");
view2131165218 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.btn_sideslip();
}
});
}
@Override
@CallSuper
public void unbind() {
if (this.target == null) throw new IllegalStateException("Bindings already cleared.");
view2131165218.setOnClickListener(null);
view2131165218 = null;
this.target = null;
}
}
从上面代码我们发现是DebouncingOnClickListener而不是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);
}
作用是当一个Button被按下是,其他的Button都disable。
我们在这里看下们要看下ButterKnife.bind()方法的全家福。
//适用于activity,view注入
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
//在view中绑定子view使用
@NonNull @UiThread
public static Unbinder bind(@NonNull View target) {
return createBinding(target, target);
}
//适用于Dialog,view注入
@NonNull @UiThread
public static Unbinder bind(@NonNull Dialog target) {
View sourceView = target.getWindow().getDecorView();
return createBinding(target, sourceView);
}
//适用于在Activity外部使用Activity的view
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull Activity source) {
View sourceView = source.getWindow().getDecorView();
return createBinding(target, sourceView);
}
//适用于Fragment中,view注入
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
return createBinding(target, source);
}
//在dialog外部使用Dialog的view
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) {
View sourceView = source.getWindow().getDecorView();
return createBinding(target, sourceView);
}
用注解修饰的变量,和方法都不能试private,这是为什么呢
我们可以看一下,MainActivity_ViewBinding的包名,与MainActivity是相同的。而 MainActivity_ViewBinding使用了MainActivity中的btn_sideslip变量和btn_sideslip方法,如果设置成private会导致编译出错。所以不能试private的。在生成MainActivity_ViewBinding时会逐一检查注解修饰的所有变量和方法是否是private,如果是直接报错。
总结一下原理:
ButterKnife所注入注解框架是运行时注解,即声明注解的生命周期为RUNTIME,然后在运行的时候通过反射完成注入,这种方式虽然简单,但是这种方式多多少少会有性能的损耗。那么有没有一种方法能解决这种性能的损耗呢? 没错,答案肯定是有的,那就是Butterknife用的APT(Annotation Processing Tool)编译时解析技术。
APT大概就是你声明的注解的生命周期为CLASS,然后继承AbstractProcessor类。继承这个类后,在编译的时候,编译器会扫描所有带有你要处理的注解的类,然后再调用AbstractProcessor的process方法,对注解进行处理,那么我们就可以在处理的时候,动态生成绑定事件或者控件的java代码,然后在运行的时候,直接调用bind方法完成绑定。
其实这种方式的好处是我们不用再一遍一遍地写findViewById和onClick了,这个框架在编译的时候帮我们自动生成了这些代码,然后在运行的时候调用就行了。
基本流程图:

本文深入探讨ButterKnife的工作原理,包括注解处理工具APT的应用、注解生命周期、反射技术等,展示了如何实现视图绑定及事件绑定。

6335

被折叠的 条评论
为什么被折叠?



