0
点赞
收藏
分享

微信扫一扫

ButterKnife 源码解析 (一)

书呆鱼 2022-09-14 阅读 86


文章目录

  • ​​1. ButterKnife 的优势​​
  • ​​2. APT 和 IoC 架构的区别​​
  • ​​3. ButterKnife 用法​​
  • ​​4. ButterKnife 原理​​
  • ​​4.1 代码中简单使用示例​​
  • ​​4.2 源码分析​​
  • ​​4.3 再从生成的 MainActivity_ViewBinding 类分析​​

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()​​方法的流程:

  1. 首先获取当前​​Activity​​​ 的​​DecorView​​​,​​DecorView​​​ 是整个​​ViewTree​​​ 的最顶层​​View​​​,包含标题​​view​​​ 和内容​​view​​​ 这两个子元素。我们一直调用的​​setContentView()​​​ 方法其实就是往内容​​view​​​ 中添加​​view​​ 元素。
  2. 然后调用​​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;
}
..
}


举报

相关推荐

0 条评论