上篇文章中?我们看了ButterKnife的源码文档,看到了如何使用,那么这篇文章我们来分析具体原理。还是以activity中使用为例:
public class ExampleActivity extends Activity {
//给需要初始化的view添加注解,参数为id
@BindView(R.id.title)
EditText titleView;
@BindView(R.id.subtitle)
EditText subtitleView;
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.example_activity);
//绑定,而且要在setcontentview后面
ButterKnife.bind(this);
}
}
ButterKnife.bind(this)方法 :
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
//获取根view
View sourceView = target.getWindow().getDecorView();
//调用重载方法
return bind(target, sourceView);
}
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
//获取view的Class类
Class<?> targetClass = target.getClass();
//根据Class去找一个构造器,什么构造器后面我们看
Constructor<? extends Unbinder> constructor =
findBindingConstructorForClass(targetClass);
//没找到,则返回null,说明失败了
if (constructor == null) {
return Unbinder.EMPTY;
}
//通过反射创建实例对象
return constructor.newInstance(target, source);
}
第一步:获取根view的Class
第二步:通过根View的Class找反射创建对象用的构造器
第三步:如果没找到,则返回EMPTY对象,找到了则通过反射创建对象
第二步是如何查找的:
@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//从解析缓存中查找,如果找到了直接返回
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
return bindingCtor;
}
//获取cls的全类名
String clsName = cls.getName();
//系统类直接跳过
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
return null;
}
try {
//通过全类名+_ViewBinding去加载这个类
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
//noinspection unchecked
//获取加载类的构造器
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} catch (ClassNotFoundException e) {
//没有找到则向父类查找
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
}
//将解析过查找到的缓存起来
BINDINGS.put(cls, bindingCtor);
//返回构造器
return bindingCtor;
}
第一步:从全局缓存中查找,找到了直接返回
第二步:判断是不是系统类,如果是则直接返回null
第三步:主动加载类名为cla的全类名+_ViewBinding的类,并获取构造器
第四步:如果加载类或者获取构造器出错了,则向上查找
第五步:全局缓存解析查找结果,并返回反射构造器。也就是说第一次加载的时候会慢一些,但是第二次开始就比较快了。
通过上面五个步骤的分析,我们可以得出如下结论:
1.activity或者是fragment中,不需要解绑,不会发生内存泄漏。因为我们缓存解析结果都是用的Class类对象为key的。
2.系统类不可以使用这个框架(为什么,没想明白)
3.每个使用类都会有一个对应的cla的全类名+_ViewBinding的类存在,因为要创建实例。
至此,bind方法分析完了。可以我们并没有写cla的全类名+_ViewBinding的类啊,这个类是哪来的呢?这里我们就需要先补充个知识:编译期注解? 就是通过注解实现编译期自动生成的java代码。生成的代码位置如下:
?下面我们就看看一个最简单的activity中使用和生成的文件:
public class TestActivity extends Activity {
@BindView(R.id.base_refresh)
SmartRefreshLayout smartRefreshLayout;
@OnClick(R.id.base_refresh)
void click(View view) {
}
Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.example_activity);
//绑定,而且要在setcontentview后面
ButterKnife.bind(this);
}
}
public class TestActivity_ViewBinding implements Unbinder {
//目标activity
private TestActivity target;
//随意设置了属性名称 view7f0900b1 方便解绑时解耦
private View view7f0900b1;
@UiThread
public TestActivity_ViewBinding(TestActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public TestActivity_ViewBinding(final TestActivity target, View source) {
this.target = target;
View view;
//就是 view=source.findViewById(R.id.base_refresh);
view = Utils.findRequiredView(source, R.id.base_refresh, "field 'smartRefreshLayout' and method 'click'");
//就是类型强转 target.smartRefreshLayout=(SmartRefreshLayout)view;
target.smartRefreshLayout = Utils.castView(view, R.id.base_refresh, "field 'smartRefreshLayout'", SmartRefreshLayout.class);
view7f0900b1 = view;
//设置点击事件 DebouncingOnClickListener 这个类还可以防止快速连续点击
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
//调用我们写的点击方法
target.click(p0);
}
});
}
@Override
@CallSuper
public void unbind() {
//解耦操作
TestActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.smartRefreshLayout = null;
view7f0900b1.setOnClickListener(null);
view7f0900b1 = null;
}
}
解释都在上面了。其实内部就是帮我们实现了findviewbyid和设置点击事件。通过上面分析我们可以得出:
1.每个点击注解都会帮我们实现点击事件,创建一个对象,所以其实不是很好,不过可以忽略
2.虽然我们在使用中不主动调用unbind方法不会发生内存泄漏,但是最好还是不用的时候调用一下。
至此源码分析完毕。
那么我们能学到什么?
1.最重要的就是学到了编译期注解的实际应用案例,以后可以参考这个例子来写我们自己的编译期注解代码,自动帮我们解决一些重复而简单的工作。
2.源码中还有一个防止快速点击的代码,我觉得也很值得学习记录一下:
public abstract class DebouncingOnClickListener implements View.OnClickListener {
static boolean enabled = true;
private static final Runnable ENABLE_AGAIN = () -> 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);
}
|