目录
[二、生成 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)。如果找不到,编译会直接报错。依赖追踪:编译器会分析出,当前这个
TextView的android: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);
}
这个sMapper是 DataBinderMapper 的实例 。在编译时,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()。这会强制跳过等待,在当前时间点立刻执行所有待处理的绑定更新
目前文章篇幅有限,后续会再写一章来继续探究事件绑定,相关注解等源码