“魔法胶水”之BindingAdapter 的实现原理浅析

51 阅读2分钟

故事开场:一场“魔法胶水”的招聘大会

想象你是一位刚入职的 Android 小镇的“UI 魔法师”。
你的任务是把**数据(Model)界面(View)**粘在一起,让它们像情侣一样同步更新。
但小镇里有个规矩:

绝对不能在 XML 里写 Java 代码!

于是,镇长 androidx.databinding 开了一家“魔法胶水工厂”,专门生产一种叫
@BindingAdapter 的魔法贴纸。
只要把它贴在任何一个静态方法上,工厂就能把它注册成“属性→方法”的胶水。
之后,你在 XML 里写 app:xxx="",魔法胶水就会“啪”一下,自动帮你调用那个方法,把数据塞进 View。


角色表(先认个脸)

角色真实类名作用
魔法贴纸@BindingAdapter标记“这是一个胶水方法”
胶水工厂BindingAdapterStore存放所有贴纸的映射表
翻译官ViewDataBinding把 XML 里的表达式翻译成 Java 调用
包工头DataBinderMapperImpl(自动生成)编译期收集所有贴纸,生成索引表
小精灵BindingUtil运行期负责“找胶水、涂胶水”

故事正文:一次“加载图片”的魔法之旅

1. 你写了一个“胶水方法”

public class ImageViewAdapter {

    // 魔法贴纸:把 “imageUrl” 这个 XML 属性 → 这个静态方法
    @BindingAdapter("imageUrl")
    public static void loadUrl(ImageView view, String url) {
        Glide.with(view).load(url).into(view);
    }
}

编译期,注解处理器 BindingProcessor 看到贴纸,立刻记录:

“imageUrl” → ImageViewAdapter.loadUrl(ImageView,String)

2. 包工头生成“胶水目录”

编译后,在 build/generated/source/kapt/... 里会蹦出一个类:

public class DataBinderMapperImpl extends DataBinderMapper {
    @Override
    public List<BindingAdapterStore> collectStore() {
        BindingAdapterStore store = new BindingAdapterStore();
        store.put("imageUrl", 
                  new ResolvedBindingAdapter(
                      ImageViewAdapter.class, 
                      "loadUrl", 
                      ImageView.class, String.class));
        return Collections.singletonList(store);
    }
}

这就是一本“胶水黄页”:谁需要什么属性,对应哪个方法,写得明明白白。

3. 你在 XML 里“下订单”

<ImageView
    android:id="@+id/avatar"
    android:layout_width="64dp"
    android:layout_height="64dp"
    app:imageUrl="@{user.avatar}" />

翻译官 ActivityMainBinding(自动生成)看到 @{user.avatar}
会在 executeBindings() 里生成类似代码:

// 伪代码
String url = user.avatar;
ImageViewAdapter.loadUrl(this.avatar, url);

真正的调用不是直接写死,而是通过“胶水黄页”动态查找——
这样你换库、改名都不怕。

4. 运行期:小精灵涂胶水

时序图

BindingAdapter.png


问答时间

Q1:为什么方法必须是 static?
→ 小精灵没有“new 你”的预算,直接 Class.method() 最快。

Q2:可以一贴多属性吗?
→ 可以!@BindingAdapter({"android:src", "android:tint"})
相当于“一张贴纸两面胶”。

Q3:如果写错了属性名?
→ 编译期就报错:Cannot find a setter for app:imageUlr
镇长直接把你拦在城门口。


彩蛋:偷看胶水黄页

调试用一行代码,把整张表打印出来:

Log.d("BindingAdapters", 
      DataBindingUtil.getDefault().getKnownAdapters().toString());

你会看到一堆:

imageUrl -> ImageViewAdapter.loadUrl(...)
android:text -> TextViewBindingAdapter.setText(...)
...

故事小结

  1. 你贴 @BindingAdapter → 编译期进入“胶水黄页”。
  2. XML 写 app:xxx="@{...}" → 运行期小精灵查表、反射调用。
  3. 从此 数据变化 → 自动刷新 UI,无需手写 findViewByIdsetText

镇长 androidx.databinding 拍拍你的肩膀:
“去吧,少年!用魔法贴纸,把界面和数据粘成一对永不分手的恋人。”