Android DataBinding 全面解析【源码篇1】

目录

一、为什么会自动生成?(编译期之前)

[二、生成 ActivityMainBinding(编译期)](#二、生成 ActivityMainBinding(编译期))

[2.1 它到底干了些什么?](#2.1 它到底干了些什么?)

[2.1.1 剥离与提纯:生成标准布局文件](#2.1.1 剥离与提纯:生成标准布局文件)

[2.1.2 标记与埋点:植入识别 Tag](#2.1.2 标记与埋点:植入识别 Tag)

[2.1.3 建档与映射:生成元数据文件](#2.1.3 建档与映射:生成元数据文件)

[2.2 最终生成了什么?](#2.2 最终生成了什么?)

[2.2.1 BR.java (Binding Resources)](#2.2.1 BR.java (Binding Resources))

[2.2.2 DataBinderMapperImpl.java (映射路由表)](#2.2.2 DataBinderMapperImpl.java (映射路由表))

[2.2.3 抽象绑定类 [LayoutName]Binding.java](#2.2.3 抽象绑定类 [LayoutName]Binding.java)

[2.2.4 具体实现类 [LayoutName]BindingImpl.java (真正的核心)](#2.2.4 具体实现类 [LayoutName]BindingImpl.java (真正的核心))

[三、Acitivity 绑定(运行期)](#三、Acitivity 绑定(运行期))

[3.1 阶段一:初始化绑定](#3.1 阶段一:初始化绑定)

[3.1.1 DataBindingUtil.setContentView() ------ Activity 专属路径](#3.1.1 DataBindingUtil.setContentView() —— Activity 专属路径)

[3.1.2 ActivityMainBinding.inflate(...) ------ 通用路径](#3.1.2 ActivityMainBinding.inflate(...) —— 通用路径)

[3.2 阶段二:数据变更与 UI 刷新](#3.2 阶段二:数据变更与 UI 刷新)


系列入口导航:Android Jetpack 概述

在上一篇文章中,我们了解了 DataBinding 的基本使用 ,在使用的过程当中,觉得 DataBinding 很神秘,因为写完 XML 后,代码里会自动出现 ActivityMainBinding 。接下来我们从编译期前,编译期和运行期的角度来看看 DataBinding 更深层的逻辑。

一、为什么会自动生成?(编译期之前)

ActivityMainBinding 的出现是因为 Android Gradle 插件在编译代码之前先对你的 XML 布局文件进行了一次"深度扫描"和"重新加工"。我创建了一个新的 MainActivity2 ,使用自动生成的 activity_main2.xml 也没发现 ActivityMain2Binding(大驼峰命名法),这是什么原因呢?我们接着探究。

因为 activity_main2.xml 此时没有 <layout> 标签,扫描的过程中不认为这个布局是 DataBinding 布局。

后面加上了<layout>就可以搜索到了,这也太快了吧!!!难道 ActivityMain2Binding 是动态生成的吗可是我在 build\generated\data_binding_base_class_source_out 目录没有找到对于文件

  • 实时解析:IDE 内部有一个轻量级的 XML 解析器,它会时刻监控你的 res/layout 目录。
  • 虚拟符号(In-Memory Symbols ):一旦它看到 <layout>,它会在内存中立刻构造出一个"虚拟类"的符号。它知道根据规则,activity_main2.xml 应该对应 ActivityMain2Binding。
  • 代码补全服务:当你搜索或编写代码时,IDE 的代码补全引擎(Code Completion)会把这个内存中的虚拟类推荐给你。

类似的情况还有 <Variable> 标签中对应的 get set 方法,和View 中对对应的 android:id。

二、生成 ActivityMainBinding(编译期)

从获取到xml 文件到生成对应 Binding 类 都是databinding-compiler 在干活,databinding-compiler‌ 是 Android Data Binding 框架中的关键编译器组件,负责在构建时解析 XML 布局文件,生成用于数据绑定的 Java/Kotlin 绑定类(如 ActivityMainBinding)。

2.1 它到底干了些什么?

Android Gradle Plugin 会剥离 <layout> 标签,将原始 XML 转换为标准的 Android 布局,同时生成一个中间描述文件(XML 文件),记录了变量与 View 的映射关系。

databinding-compiler‌ 它并不直接读 XML 源码 ,而是读取由LayoutXmlProcessor预处理后生成的 XXX-layout.xml

我们可以将它的工作流程分为三个阶段:

2.1.1 剥离与提纯:生成标准布局文件

首先,编译器会处理 <layout> 和 <data> 等 DataBinding 专用标签。它会将这些标签剥离只保留其中的 UI 组件树部分(如 LinearLayout、TextView 等) ,从而生成一个原生的 Android 布局文件。这个文件是后续系统进行界面渲染的基础,因为 Android 系统本身无法识别 @{user.name} 这样的表达式

2.1.2 标记与埋点:植入识别 Tag

在剥离的同时,编译器会对布局中的部分特定 View 植入 android:tag。被修改的 View 主要分为三类:

  • 根布局: 被植入 layout/布局名_0,用于校验和定位绑定的起点。
  • 使用了绑定表达式 @{} 的 View: 被植入 binding_索引,以便精确找到它们来更新数据。
  • <include> 节点: 同样会被打上 tag,用于建立父子 Binding 类之间的级联联系。

注:仅有 android:id 的纯静态 View 不会被植入 tag,而是通过维护一个 ID -> 数组索引 的映射表,在遍历时直接通过 getId() 匹配

植入 tag 的索引分配是按照 XML 的声明顺序(深度优先)依次递增的。运行时的 mapBindings 方法只需一次深度优先遍历,就能把所有带 tag 的 View 以及带目标 ID 的 View 全部捞出来,彻底消灭了低效的多次 findViewById。

2.1.3 建档与映射:生成元数据文件

最后,编译器会生成一个关键的中间文件(通常后缀为 -layout.xml,位于 build/intermediates 目录下)。这个文件并不直接参与运行,它更像一份结构化的 "映射说明书" ,记录了以下关键信息 :

  • View 与 Tag 的对应关系:哪个控件对应哪个 binding_索引值。
  • 变量声明:你在 <data> 标签里定义的 variable 的名称和类型。
  • 绑定表达式详情:记录了 android:text="@{user.name}" 具体要绑定到哪个类的哪个属性上。

这份"说明书"是下一步(编译期)生成 ActivityMainBinding.java 等核心绑定类的直接依据,确保了代码生成的准确性。

当编译器扫描到 android:text="@{user.name}" 时 ,它会提取引号内的字符串 user.name。随后,它会将这段字符串解析成一个抽象语法树(Abstract Syntax Tree,简称 AST)。

在这个树状结构中,user.name 被解构为:根节点是一个 属性访问表达式,它的左子树是 标识符 user,右子树是 标识符 name。这样就清晰地表达了"获取 user 对象的 name 属性"这一意图 。

有了 AST,编译器就能进行"意义"上的校验:

  • 类型溯源:编译器会去 <data> 标签的 <variable><import> 记录中,查找标识符 user 的类型(如 User.java)。如果找不到,编译会直接报错。

  • 依赖追踪:编译器会分析出,当前这个 TextViewandroid:text 属性,依赖于 User 类中的 getName() 方法(假设 name 是私有字段)。

  • 表达式合法性校验:编译器会检查 User 类中是否存在 name 对应的 getter 方法。如果找不到 getName(),编译同样会报错。其他表达式(如三目运算 @{user.age > 18 ? "成年" : "未成年"})的类型匹配校验也在此完成。

最后一步,也是最核心的一步,编译器会根据上一步的元数据和语法树,使用 JavaPoet(或类似的字节码/源码生成工具)生成大量的 Java 类文件。

2.2 最终生成了什么?

你可以通过切换到 Android Studio 的 Project 视图,在 app/build/generated/source/kapt/ 或 app/build/generated/ap_generated_sources/ 目录下找到这些生成的文件。

主要会生成以下四类核心文件:

2.2.1 BR.java (Binding Resources)

类似于 Android 自动生成的 R.java,BR.java 包含了所有你在 XML 中定义的 <variable> 以及 @Bindable 注解属性整型 ID

java 复制代码
// 生成的代码示例
public class BR {
  public static final int _all = 0;
  public static final int user = 1;
}

作用: 用于在属性发生变化时(notifyPropertyChanged(BR.user)),作为唯一标识符通知 UI 更新。

2.2.2 DataBinderMapperImpl.java (映射路由表)

这是一个内部注册表。

作用 : 当你在代码中调用 DataBindingUtil.setContentView(this, R.layout.activity_main) 时,框架怎么知道 R.layout.activity_main 应该对应哪个 Binding 类?就是通过这个类里的SparseArray 或者 switch-case查表得知的。

java 复制代码
public class DataBinderMapperImpl extends DataBinderMapper {
  private static final int LAYOUT_ACTIVITYMAIN = 1;

  private static final int LAYOUT_ACTIVITYMAIN2 = 2;

  private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(2);

  static {
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.myjetpack.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.myjetpack.R.layout.activity_main2, LAYOUT_ACTIVITYMAIN2);
  }

  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
      final Object tag = view.getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
        case  LAYOUT_ACTIVITYMAIN: {
          if ("layout/activity_main_0".equals(tag)) {
            return new ActivityMainBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
        }
        case  LAYOUT_ACTIVITYMAIN2: {
          if ("layout/activity_main2_0".equals(tag)) {
            return new ActivityMain2BindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_main2 is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }

  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View[] views, int layoutId) {
    if(views == null || views.length == 0) {
      return null;
    }
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
      final Object tag = views[0].getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
      }
    }
    return null;
  }

  @Override
  public int getLayoutId(String tag) {
    if (tag == null) {
      return 0;
    }
    Integer tmpVal = InnerLayoutIdLookup.sKeys.get(tag);
    return tmpVal == null ? 0 : tmpVal;
  }

  @Override
  public String convertBrIdToString(int localId) {
    String tmpVal = InnerBrLookup.sKeys.get(localId);
    return tmpVal;
  }

  @Override
  public List<DataBinderMapper> collectDependencies() {
    ArrayList<DataBinderMapper> result = new ArrayList<DataBinderMapper>(1);
    result.add(new androidx.databinding.library.baseAdapters.DataBinderMapperImpl());
    return result;
  }

  private static class InnerBrLookup {
    static final SparseArray<String> sKeys = new SparseArray<String>(2);

    static {
      sKeys.put(0, "_all");
      sKeys.put(1, "user");
    }
  }

  private static class InnerLayoutIdLookup {
    static final HashMap<String, Integer> sKeys = new HashMap<String, Integer>(2);

    static {
      sKeys.put("layout/activity_main_0", com.example.myjetpack.R.layout.activity_main);
      sKeys.put("layout/activity_main2_0", com.example.myjetpack.R.layout.activity_main2);
    }
  }
}

2.2.3 抽象绑定类 [LayoutName]Binding.java

如果你有一个布局叫 activity_main.xml,编译器会在 databinding 包下生成一个 ActivityMainBinding.java 的抽象类

  • 内容 : 包含了该布局中所有有 ID 的 View 的 public final 引用(避免了 findViewById),以及声明了对应 <variable> 的 abstract set/get 方法
  • 作用: 提供给开发者在 Java/Kotlin 代码中调用的 API 接口。
java 复制代码
public abstract class ActivityMainBinding extends ViewDataBinding {
  @NonNull
  public final Button btnUpdate;

  @NonNull
  public final TextView tvName;

  @Bindable
  protected User mUser;

  protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount,
      Button btnUpdate, TextView tvName) {
    super(_bindingComponent, _root, _localFieldCount);
    this.btnUpdate = btnUpdate;
    this.tvName = tvName;
  }

  public abstract void setUser(@Nullable User user);

  @Nullable
  public User getUser() {
    return mUser;
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup root, boolean attachToRoot) {
    return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
  }

  /**
   * This method receives DataBindingComponent instance as type Object instead of
   * type DataBindingComponent to avoid causing too many compilation errors if
   * compilation fails for another reason.
   * https://issuetracker.google.com/issues/116541301
   * @Deprecated Use DataBindingUtil.inflate(inflater, R.layout.activity_main, root, attachToRoot, component)
   */
  @NonNull
  @Deprecated
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup root, boolean attachToRoot, @Nullable Object component) {
    return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, root, attachToRoot, component);
  }

  @NonNull
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, DataBindingUtil.getDefaultComponent());
  }

  /**
   * This method receives DataBindingComponent instance as type Object instead of
   * type DataBindingComponent to avoid causing too many compilation errors if
   * compilation fails for another reason.
   * https://issuetracker.google.com/issues/116541301
   * @Deprecated Use DataBindingUtil.inflate(inflater, R.layout.activity_main, null, false, component)
   */
  @NonNull
  @Deprecated
  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable Object component) {
    return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, null, false, component);
  }

  public static ActivityMainBinding bind(@NonNull View view) {
    return bind(view, DataBindingUtil.getDefaultComponent());
  }

  /**
   * This method receives DataBindingComponent instance as type Object instead of
   * type DataBindingComponent to avoid causing too many compilation errors if
   * compilation fails for another reason.
   * https://issuetracker.google.com/issues/116541301
   * @Deprecated Use DataBindingUtil.bind(view, component)
   */
  @Deprecated
  public static ActivityMainBinding bind(@NonNull View view, @Nullable Object component) {
    return (ActivityMainBinding)bind(component, view, R.layout.activity_main);
  }
}

2.2.4 具体实现类 [LayoutName]BindingImpl.java (真正的核心)

这是上述抽象类的具体实现类,通常位于 databinding.impl 包下。你在 XML 里写的所有逻辑,最终都被翻译到了这个类里。

java 复制代码
public class ActivityMainBindingImpl extends ActivityMainBinding  {

    @Nullable
    private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
    @Nullable
    private static final android.util.SparseIntArray sViewsWithIds;
    static {
        sIncludes = null;
        sViewsWithIds = new android.util.SparseIntArray();
        sViewsWithIds.put(R.id.btnUpdate, 5);
    }
    // views
    @NonNull
    private final android.widget.LinearLayout mboundView0;
    @NonNull
    private final android.widget.TextView mboundView2;
    @NonNull
    private final android.widget.TextView mboundView3;
    @NonNull
    private final android.widget.TextView mboundView4;
    // variables
    // values
    // listeners
    // Inverse Binding Event Handlers

    public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 6, sIncludes, sViewsWithIds));
    }
    private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 0
            , (android.widget.Button) bindings[5]
            , (android.widget.TextView) bindings[1]
            );
        this.mboundView0 = (android.widget.LinearLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView2 = (android.widget.TextView) bindings[2];
        this.mboundView2.setTag(null);
        this.mboundView3 = (android.widget.TextView) bindings[3];
        this.mboundView3.setTag(null);
        this.mboundView4 = (android.widget.TextView) bindings[4];
        this.mboundView4.setTag(null);
        this.tvName.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

    @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x2L;
        }
        requestRebind();
    }

    @Override
    public boolean hasPendingBindings() {
        synchronized(this) {
            if (mDirtyFlags != 0) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
        if (BR.user == variableId) {
            setUser((com.example.myjetpack.User) variable);
        }
        else {
            variableSet = false;
        }
            return variableSet;
    }

    public void setUser(@Nullable com.example.myjetpack.User User) {
        this.mUser = User;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.user);
        super.requestRebind();
    }

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
        }
        return false;
    }

    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        int userAge = 0;
        com.example.myjetpack.User user = mUser;
        java.lang.String userAgeInt18JavaLangStringJavaLangString = null;
        java.lang.String stringValueOfUserAge = null;
        boolean userAgeInt18 = false;
        int userAgeInt18MboundView3AndroidColorHoloGreenDarkMboundView3AndroidColorHoloRedDark = 0;
        java.lang.String userName = null;
        int userAgeInt18ViewVISIBLEViewGONE = 0;

        if ((dirtyFlags & 0x3L) != 0) {



                if (user != null) {
                    // read user.age
                    userAge = user.getAge();
                    // read user.name
                    userName = user.getName();
                }


                // read String.valueOf(user.age)
                stringValueOfUserAge = java.lang.String.valueOf(userAge);
                // read user.age >= 18
                userAgeInt18 = (userAge) >= (18);
            if((dirtyFlags & 0x3L) != 0) {
                if(userAgeInt18) {
                        dirtyFlags |= 0x8L;
                        dirtyFlags |= 0x20L;
                        dirtyFlags |= 0x80L;
                }
                else {
                        dirtyFlags |= 0x4L;
                        dirtyFlags |= 0x10L;
                        dirtyFlags |= 0x40L;
                }
            }


                // read user.age >= 18 ? "成年人" : "未成年人"
                userAgeInt18JavaLangStringJavaLangString = ((userAgeInt18) ? ("成年人") : ("未成年人"));
                // read user.age >= 18 ? @android:color/holo_green_dark : @android:color/holo_red_dark
                userAgeInt18MboundView3AndroidColorHoloGreenDarkMboundView3AndroidColorHoloRedDark = ((userAgeInt18) ? (getColorFromResource(mboundView3, android.R.color.holo_green_dark)) : (getColorFromResource(mboundView3, android.R.color.holo_red_dark)));
                // read user.age >= 18 ? View.VISIBLE : View.GONE
                userAgeInt18ViewVISIBLEViewGONE = ((userAgeInt18) ? (android.view.View.VISIBLE) : (android.view.View.GONE));
        }
        // batch finished
        if ((dirtyFlags & 0x3L) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, stringValueOfUserAge);
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView3, userAgeInt18JavaLangStringJavaLangString);
            this.mboundView3.setTextColor(userAgeInt18MboundView3AndroidColorHoloGreenDarkMboundView3AndroidColorHoloRedDark);
            this.mboundView4.setVisibility(userAgeInt18ViewVISIBLEViewGONE);
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvName, userName);
        }
    }
    // Listener Stub Implementations
    // callback impls
    // dirty flag
    private  long mDirtyFlags = 0xffffffffffffffffL;
    /* flag mapping
        flag 0 (0x1L): user
        flag 1 (0x2L): null
        flag 2 (0x3L): user.age >= 18 ? "成年人" : "未成年人"
        flag 3 (0x4L): user.age >= 18 ? "成年人" : "未成年人"
        flag 4 (0x5L): user.age >= 18 ? @android:color/holo_green_dark : @android:color/holo_red_dark
        flag 5 (0x6L): user.age >= 18 ? @android:color/holo_green_dark : @android:color/holo_red_dark
        flag 6 (0x7L): user.age >= 18 ? View.VISIBLE : View.GONE
        flag 7 (0x8L): user.age >= 18 ? View.VISIBLE : View.GONE
    flag mapping end*/
    //end
}

下面用一个简单的例子演示下:

为了让你彻底看懂,我们假设 XML 是这样的:

XML 复制代码
<layout>
  <data>
    <variable name="user" type="com.example.User" />
  </data>
  <LinearLayout>
      <TextView android:id="@+id/nameText" android:text="@{user.name}" />
  </LinearLayout>
</layout>

编译器最终生成的 ActivityMainBindingImpl.java 的核心结构大致如下(已简化):

java 复制代码
public class ActivityMainBindingImpl extends ActivityMainBinding {

    // 1. 脏标记 (Dirty Flags):用于记录哪些变量发生了改变
    // 这是一个位掩码,第0位代表user变了,第1位代表其他变量变了...
    private long mDirtyFlags = 0xffffffffffffffffL;

    public ActivityMainBindingImpl(DataBindingComponent component, View root) {
        super(component, root, 1 /* bindings count */);
        // ... 初始化 View,根据之前生成的 android:tag 绑定真实的 View
        this.nameText.setTag(null);
    }

    // 2. 变量 Setter
    @Override
    public void setUser(com.example.User user) {
        this.mUser = user;
        // 使用按位或运算,标记第 0 位为 1,表示 user 变了 (即 BR.user)
        mDirtyFlags |= 0x1L;
        // 请求重新绑定UI (通常是在下一帧执行)
        requestRebind();
    }

    // 3. 核心执行方法:所有的 UI 更新都在这里发生!!!
    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0; // 重置脏标记
        }

        String userName = null;
        com.example.User user = mUser;

        // 4. 评估表达式
        // 检查第 0 位是否为 1 (即检查 user 是否变了)
        if ((dirtyFlags & 0x1L) != 0) {
            if (user != null) {
                // 读取属性 (如果是 Observable 还会注册监听器)
                userName = user.getName();
            }
        }

        // 5. 应用到 View 上
        if ((dirtyFlags & 0x1L) != 0) {
            // 调用 Android 框架的 API 或者你自定义的 BindingAdapter
            // 这就相当于你手动写的 findViewById().setText()
            TextViewBindingAdapter.setText(this.nameText, userName);
        }
    }
}

三、Acitivity 绑定(运行期)

好的,我们接着编译时的处理流程,来看 DataBinding 在运行时是如何将 View 和数据连接起来,并实现自动更新的

运行时流程可以分为两大核心阶段:初始化绑定(建立联系)数据变更与 UI 刷新(动态响应)

3.1 阶段一:初始化绑定

当你在 Activity 或 Fragment 中通过DataBindingUtil.setContentView() 或 **DataBindingUtil.inflate()**加载布局时,DataBinding 框架在背后主要做了这几件事 :

  • 加载布局并查找 View:首先生成标准的 View 层级。然后调用自动生成的 ActivityMainBinding 类中的 mapBindings 方法,这个方法会依赖我们在编译期植入的 android:tag,对 View 树进行一次性深度优先遍历,将所有带 tag 的 View 和带 ID 的 View 一一找出并存入数组 。
  • 创建 Binding 实例:实例化 ActivityMainBinding 对象。在这个过程中,它将所有带 ID 的 View 都声明为 public final 成员变量,方便我们直接通过 binding.tvName 的方式访问,彻底告别 findViewById 。
  • 完成首次数据绑定:Binding 类实例化后,会自动触发一次 invalidateAll() → requestRebind() 流程,最终调用 executeBindings() 方法,将我们在代码中通过 binding.setUser(user) 设置进来的数据,更新到对应的 View 上,完成界面的首次渲染 。

下面分别用两条初始化路径来描述下:

3.1.1 DataBindingUtil.setContentView() ------ Activity 专属路径

当你调用 DataBindingUtil.setContentView(activity, layoutId) 时,它的走法如下:

  • 1. 标准加载:它内部首先会调用 activity.setContentView(layoutId) 。这触发了 Android 系统标准的布局加载流程,将 XML 里的 View 树挂载到 DecorView 上
  • 2. 获取 ContentRoot(获取根视图):DataBinding 会通过 activity.findViewById(android.R.id.content) 拿到 Activity 的内容容器,并获取它的第一个子 View(即你的布局根节点)。

前两步的代码如下:

java 复制代码
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
                                                           int layoutId, @Nullable DataBindingComponent bindingComponent) {
    activity.setContentView(layoutId);
    View decorView = activity.getWindow().getDecorView();
    ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
    return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
  • 3.调用 bind() 方法:拿到这个根 View 之后,它会转而调用 DataBindingUtil.bind 。
java 复制代码
private static <T extends ViewDataBinding> T bindToAddedViews
        (DataBindingComponent component,
         ViewGroup parent, int startChildren, int layoutId) {
    final int endChildren = parent.getChildCount();
    final int childrenAdded = endChildren - startChildren;
    if (childrenAdded == 1) {
        final View childView = parent.getChildAt(endChildren - 1);
        return bind(component, childView, layoutId);
    } else {
        final View[] children = new View[childrenAdded];
        for (int i = 0; i < childrenAdded; i++) {
            children[i] = parent.getChildAt(i + startChildren);
        }
        return bind(component, children, layoutId);
    }
}



@SuppressWarnings("unchecked")
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
                                          int layoutId) {
    return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
}

static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
                                          int layoutId) {
    return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}

这个sMapperDataBinderMapper 的实例 。在编译时,DataBinding 会生成一个具体的实现类,名字通常叫DataBinderMapperImpl

  • 4.跳转到 DataBinderMapperImpl:通过分拣的方式来初始化 对应 ActivityMainBindingImpl 。
java 复制代码
  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
      final Object tag = view.getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
        case  LAYOUT_ACTIVITYMAIN: {
          if ("layout/activity_main_0".equals(tag)) {
            return new ActivityMainBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
        }
        case  LAYOUT_ACTIVITYMAIN2: {
          if ("layout/activity_main2_0".equals(tag)) {
            return new ActivityMain2BindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_main2 is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }

  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View[] views, int layoutId) {
    if(views == null || views.length == 0) {
      return null;
    }
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
      final Object tag = views[0].getTag();
      if(tag == null) {
        throw new RuntimeException("view must have a tag");
      }
      switch(localizedLayoutId) {
      }
    }
    return null;
  }
  • 5.核心:mapBindings & 实例化:最后进入 ActivityMainBindingImpl 的构造函数,触发 mapBindings 扫描 View 树,完成变量初始化。
java 复制代码
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
    this(bindingComponent, root, mapBindings(bindingComponent, root, 6, sIncludes, sViewsWithIds));
}

3.1.2 ActivityMainBinding.inflate(...) ------ 通用路径

当你调用 ActivityMainBinding.inflate(...) 时,流程有所不同:

  • 1.直接入口:你直接调用了生成的静态方法 ActivityMainBinding.inflate。
java 复制代码
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, DataBindingUtil.getDefaultComponent());
}

/**
 * This method receives DataBindingComponent instance as type Object instead of
 * type DataBindingComponent to avoid causing too many compilation errors if
 * compilation fails for another reason.
 * https://issuetracker.google.com/issues/116541301
 * @Deprecated Use DataBindingUtil.inflate(inflater, R.layout.activity_main, null, false, component)
 */
@NonNull
@Deprecated
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
                                          @Nullable Object component) {
    return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, null, false, component);
}
  • 2.内部私有调用:接着调动**inflateInternal()**它内部会调用 DataBindingUtil.inflate()。
java 复制代码
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
protected static <T extends ViewDataBinding> T inflateInternal(
        @NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
        boolean attachToParent, @Nullable Object bindingComponent) {
    return DataBindingUtil.inflate(
            inflater,
            layoutId,
            parent,
            attachToParent,
            checkAndCastToBindingComponent(bindingComponent)
    );
}
  • 3.精准绑定:又回到了DataBindingUtil的逻辑,后续同样通过 sMapper 路由,执行 new ActivityMainBindingImpl(..., rootView)。
java 复制代码
public static <T extends ViewDataBinding> T inflate(
        @NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
        boolean attachToParent, @Nullable DataBindingComponent bindingComponent) {
    final boolean useChildren = parent != null && attachToParent;
    final int startChildren = useChildren ? parent.getChildCount() : 0;
    final View view = inflater.inflate(layoutId, parent, attachToParent);
    if (useChildren) {
        return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);
    } else {
        return bind(bindingComponent, view, layoutId);
    }
}
  • 手动挂载:当你执行 setContentView(binding.getRoot()) 时,Activity 才正式接手这个已经"全副武装"(View 引用已找齐)的 View 树。

3.2 阶段二:数据变更与 UI 刷新

DataBinding 实现数据驱动 UI 更新的关键在于,它不是实时刷新,而是做了异步优化。

  • 核心机制:当你调用 binding.setUser(user) 或修改 ObservableField 的值时,底层并不会立即操作 View,而是会标记数据"脏了",并向系统注册一个回调,请求在下一帧画面渲染前再批量更新
java 复制代码
public void setUser(@Nullable com.example.myjetpack.User User) {
    // 1. 将新数据挂载到 Binding 实例内部成员变量上
    this.mUser = User;

    // 2. 更新"脏位(Dirty Flags)"
    // 使用 synchronized 确保多线程环境下位运算的原子性
    synchronized(this) {
        // 0x1L 是一个 64 位的 Long 型掩码(Mask)
        // 这里表示将第一位标记为 1,代表 "User" 这个属性变脏了,需要重新计算绑定关系
        mDirtyFlags |= 0x1L;
    }

    // 3. 通知观察者属性已改变
    // BR.user 是编译时生成的 ID。此方法会触发所有监听该属性的观察者回调,
    // 如果是双向绑定,这里也会同步更新逆向绑定的数据。
    notifyPropertyChanged(BR.user);

    // 4. 请求重新绑定(核心异步机制)
    // 这是一个父类方法,它不会立即执行 UI 刷新,
    // 而是通过 Choreographer 向主线程发一个待办任务(Runnable),
    // 确保在下一个显示帧到来之前,合并所有的更新请求,最后统一执行 executeBindings()。
    super.requestRebind();
}
  • 帧回调与批量更新:这个机制在 Android 4.1 以上是通过 Choreographer 实现的。它会等待垂直同步信号,在合适的时机触发**executePendingBindings()**方法,统一执行所有待处理的 executeBindings() 逻辑。这种批量、异步的处理方式,能有效避免频繁地操作 UI 导致的性能问题,比如在 for 循环里多次修改数据,UI 也只会刷新一次。
  • 如何立即刷新:如果你在某些场景下(比如测量 View 宽高前)需要立即看到 UI 变化,可以手动调用 binding.executePendingBindings()。这会强制跳过等待,在当前时间点立刻执行所有待处理的绑定更新

目前文章篇幅有限,后续会再写一章来继续探究事件绑定,相关注解等源码

相关推荐
浮生世界36 分钟前
Android 动态替换桌面 Logo 实践记录(`activity-alias`)
android
海天鹰40 分钟前
字符串数组保存到Map使用避免超出范围崩溃
android
su_ym81101 小时前
Android 与 Linux 对比
android·linux·framework
默|笙1 小时前
【Linux】线程同步与互斥_日志与线程池
android·linux·运维
fengci.1 小时前
蜀道山2024上半部分
android
aq55356001 小时前
Laravel 1.x:现代PHP框架的雏形
android
XiaoLeisj2 小时前
Android 短视频播放详情页实战:从播放器模块拆分、Media3 与 FlowHelper 接入,到 ViewPager 高度适配和详情数据联动
android·okhttp·音视频·架构设计·flowhelper
努力努力再努力wz2 小时前
【MySQL入门系列】:不只是建表:MySQL 表约束与 DDL 执行机制全解析
android·linux·服务器·数据结构·数据库·c++·mysql
陆业聪2 小时前
Prompt、Rule、Skill:被混用了一年的三个词,今天说清楚
android·人工智能·aigc