文章目录
1. ButterKnife 的优势
- 强大的
View
绑定和Click
事件处理功能,简化代码,提升开发效率 - 方便的处理
Adapter
里的ViewHolder
绑定问题 - 运行时不影响
App
效率,使用配置方便- 不通过反射技术实现,而是通过预编译生成的
Class
类文件
- 不通过反射技术实现,而是通过预编译生成的
- 代码清晰,可读性强
2. APT 和 IoC 架构的区别
APT 简介
APT
即为 Annotation Processing Tool
,它是 javac
的一个工具,中文意思为编译时注解处理器。APT
可以用来在编译时扫描和处理注解。通过 APT
可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。
新版的 ButterKnife
使用的技术就是 APT
。
Day | IoC | APT |
---|---|---|
共同特点 | 都实现了解耦 | ~ |
核心技术 | 运行时通过反射技术 | 注解处理器技术 |
开发使用 | 两者几乎一致,不好分清楚 | ~ |
代码难易 | IoC 编程更具挑战性 | ~ |
程序稳定 | 两者暂未发现致命缺陷 | ~ |
两者缺陷 | reflect 会消耗一定性能,运行时消耗内存 | APT 会增加apk 大小,预编译期生成额外的Java 类 |
开发追求 | – | 更偏向编译器的 APT 技术 |
3. ButterKnife 用法
- https://github.com/JakeWharton/butterknife
4. ButterKnife 原理
4.1 代码中简单使用示例
public class MainActivity extends AppCompatActivity {
@BindView(R.id.activity_main_button)
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.activity_main_button)
public void click(View view){
Toast.makeText(this, "hi", Toast.LENGTH_SHORT).show();
}
}
4.2 源码分析
从 ButterKnife.bind(this)
入手吧,点进来看看:
/**
* BindView annotated fields and methods in the specified {@link Activity}. The current content
* view is used as the view root.
*
* @param target Target activity for view binding.
*/
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
再点:
/**
* BindView annotated fields and methods in the specified {@code target} using the {@code source}
* {@link View} as the view root.
*
* @param target Target class for view binding.
* @param source View root on which IDs will be looked up.
*/
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
//noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
try {
return constructor.newInstance(target, source);
} catch (..Exception e) {
...
}
}
@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;
}
String clsName = cls.getName();
if (clsName.startsWith("android.") || clsName.startsWith("java.")
|| clsName.startsWith("androidx.")) {
..
return null;
}
try {
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;
}
bind()
方法的流程:
- 首先获取当前
Activity
的DecorView
,DecorView
是整个ViewTree
的最顶层View
,包含标题view
和内容view
这两个子元素。我们一直调用的setContentView()
方法其实就是往内容view
中添加view
元素。 - 然后调用
createBinding(target, sourceView)
-->findBindingConstructorForClass(targetClass)
-->constructor.newInstance(target, source)
上面代码所做的就是发现 specified {@link Activity}
的构造方法,构造方法不为空,就 new
出来这个构造方法:
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass)//问题关键在这, 再看:
|
|
//cls 即 targetClass
//在这先加载APT生成的这个类
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
|
|
//再去获取它的构造方法,即先加载这个类,再启动它的构造方法
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
|
|
BINDINGS.put(cls, bindingCtor);
|
|
return constructor.newInstance(target, source);
这里会加载一个 clsName_ViewBinding
类,然后获取这个类里面的双参数 (Activity, View)
构造方法,最后放在BINDINGS
里面,它是一个 ma
p,主要作用是缓存。在下次使用的时候,就可以从缓存中获取到:
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
4.3 再从生成的 MainActivity_ViewBinding 类分析
4.2 分析到 constructor.newInstance(target, source)
, 可以看到它调用了 MainActivity_ViewBinding
的构造方法,即:
这个编译时生成的类位于:ButterKnifeDemo\app\build\generated\ap_generated_sources\debug\out\packagename\MainActivity_ViewBinding.java
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
view = Utils.findRequiredView(source, R.id.activity_main_button, "field 'button' and method 'click'");
target.button = Utils.castView(view, R.id.activity_main_button, "field 'button'", Button.class);
view7f070019 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.click(p0);
}
});
}
Utils
类:
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);//在这
if (view != null) {
return view;
}
..
}