简化版的DataBinding整体流程图
XML布局(含<data>标签)
│
▼
【编译期】注解处理器
│
▼
生成xxxBindingImpl类(如ActivityMainBindingImpl)
│
▼
【运行时】setContentView(DataBindingUtil)
│
▼
DataBinderMapperImpl(tag匹配到BindingImpl类)
│
▼
mapBindings(递归收集控件,生成Object[])
│
▼
xxxBindingImpl(保存View引用、生成监听器)
│
▼
executeBindings(脏标志、数据驱动刷新、双向监听)
│
▼
UI自动同步/响应
Databinding生成的xml对应关系
通过Databinding会生成两份xml文件,一份是:
在这里边,会为每一个view增加一个标签
我们后边也是通过这个tag来查找view并更新的。
另一份是:
在activity_main-layout这里边:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="app\src\main\res\layout\activity_main.xml"
isBindingData="true" isMerge="false" layout="activity_main"
modulePackage="com.derry.databinding_kt" rootNodeType="android.widget.LinearLayout">
<Variables name="studentInfo" declared="true" type="com.derry.databinding_kt.model.StudentInfo">
<location endLine="10" endOffset="63" startLine="8" startOffset="8" />
</Variables>
<Targets>
<Target tag="layout/activity_main_0" view="LinearLayout">
<Expressions />
<location endLine="88" endOffset="18" startLine="17" startOffset="4" />
</Target>
<Target id="@+id/et_name1" tag="binding_1" view="EditText">
<Expressions>
<Expression attribute="android:text" text="studentInfo.name">
<Location endLine="28" endOffset="45" startLine="28" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="28" endOffset="43" startLine="28" startOffset="28" />
</Expression>
</Expressions>
<location endLine="29" endOffset="13" startLine="24" startOffset="8" />
</Target>
<Target id="@+id/et_pwd1" tag="binding_2" view="EditText">
<Expressions>
<Expression attribute="android:text" text="studentInfo.pwd">
<Location endLine="35" endOffset="44" startLine="35" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="35" endOffset="42" startLine="35" startOffset="28" />
</Expression>
</Expressions>
<location endLine="37" endOffset="13" startLine="31" startOffset="8" />
</Target>
<Target id="@+id/et_name2" tag="binding_3" view="EditText">
<Expressions>
<Expression attribute="android:text" text="studentInfo.nameF">
<Location endLine="46" endOffset="46" startLine="46" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="46" endOffset="44" startLine="46" startOffset="28" />
</Expression>
</Expressions>
<location endLine="47" endOffset="13" startLine="42" startOffset="8" />
</Target>
<Target id="@+id/et_pwd2" tag="binding_4" view="EditText">
<Expressions>
<Expression attribute="android:text" text="studentInfo.pwdF">
<Location endLine="53" endOffset="45" startLine="53" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="53" endOffset="43" startLine="53" startOffset="28" />
</Expression>
</Expressions>
<location endLine="55" endOffset="13" startLine="49" startOffset="8" />
</Target>
<Target id="@+id/et_name3" tag="binding_5" view="EditText">
<Expressions>
<Expression attribute="android:text" text="studentInfo.nameF">
<Location endLine="64" endOffset="47" startLine="64" startOffset="12" />
<TwoWay>true</TwoWay>
<ValueLocation endLine="64" endOffset="45" startLine="64" startOffset="29" />
</Expression>
</Expressions>
<location endLine="65" endOffset="13" startLine="60" startOffset="8" />
</Target>
<Target id="@+id/et_pwd3" tag="binding_6" view="EditText">
<Expressions>
<Expression attribute="android:text" text="studentInfo.pwdF">
<Location endLine="71" endOffset="46" startLine="71" startOffset="12" />
<TwoWay>true</TwoWay>
<ValueLocation endLine="71" endOffset="44" startLine="71" startOffset="29" />
</Expression>
</Expressions>
<location endLine="73" endOffset="13" startLine="67" startOffset="8" />
</Target>
<Target tag="binding_7" view="TextView">
<Expressions>
<Expression attribute="android:text" text="studentInfo.nameF">
<Location endLine="79" endOffset="47" startLine="79" startOffset="12" />
<TwoWay>true</TwoWay>
<ValueLocation endLine="79" endOffset="45" startLine="79" startOffset="29" />
</Expression>
</Expressions>
<location endLine="80" endOffset="13" startLine="76" startOffset="8" />
</Target>
<Target tag="binding_8" view="TextView">
<Expressions>
<Expression attribute="android:text" text="studentInfo.pwdF">
<Location endLine="85" endOffset="46" startLine="85" startOffset="12" />
<TwoWay>true</TwoWay>
<ValueLocation endLine="85" endOffset="44" startLine="85" startOffset="29" />
</Expression>
</Expressions>
<location endLine="86" endOffset="13" startLine="82" startOffset="8" />
</Target>
</Targets>
</Layout>
整个这一块代码其实是由这一部分生成的:
那为什么会生成这么多代码呢?因为他需要提取布局里边的所有的控件。
DataBinding 之所以会生成两份 layout.xml 的原因
其实是很多初学者常有的疑问。DataBinding 之所以会生成两份 layout.xml 文件,主要是为了兼容原生 View 渲染和 DataBinding 的数据绑定机制。
详细解释
1. 生成的两份 XML 是什么?
-
原始 layout.xml:
- 就是你自己写的,比如
activity_main.xml,里面包裹了<layout>标签,包含<data>区块和实际布局内容。
- 就是你自己写的,比如
-
生成的 xx_layout.xml(或叫 xx_layout-layout.xml):
- 编译时 DataBinding 编译器会自动生成一份新的 XML(位于
build目录下,是只读的,手动不可见),用于适配数据绑定相关的逻辑。 - 有些实现会叫它“中间 layout 文件”或“实现 layout 文件”(如
activity_main-layout.xml)。
- 编译时 DataBinding 编译器会自动生成一份新的 XML(位于
2. 为什么需要生成两份?
-
兼容传统 View 加载机制:
- 传统的布局 inflate 过程不认识
<layout>、<data>这些 DataBinding 新增标签。
- 传统的布局 inflate 过程不认识
-
便于 DataBinding 注入中间变量和 tag:
- DataBinding 需要在每个 View 上注入特定的 tag(如 view id),用于数据与 View 的绑定关系。
- 生成的新 layout 文件里,每个 View 都会有额外的属性、tag,甚至可能插入一些隐藏的 View/变量,用于后续数据绑定代码生成。
-
分离关注点:
- 你写的 XML 主要描述结构和数据声明,生成的 XML 则是编译器帮你加工成能被普通 View 系统直接识别和渲染的“最终产品”。
3. 开发者需要关心吗?
- 正常开发完全不用关心第二份 XML。
- 编译器会自动处理,并且你只能看到、维护自己写的原始 XML。
直观流程图
你可以理解成这样:
你写的 layout/activity_main.xml (包含 <layout><data>)
│
▼
DataBinding 编译器处理
│
├── 生成 “实现用” layout (xx_layout-layout.xml, 各种 tag/变量)
│
└── 生成 Java/Kotlin 绑定类 (如 ActivityMainBinding.java)
一句话总结
DataBinding 生成的第二份 XML 本质上是为了解耦原生布局渲染和数据绑定逻辑,保障兼容性和正确性,开发者无需手动维护。
源码流程解析
一般的,我们会通过这里来设置布局:
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
之后会进入这里,这里的layoutId就是我们的根布局id:
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(16908290);
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
最终会通过bind将所有的view添加进去
private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component, ViewGroup parent, int startChildren, int layoutId) {
int endChildren = parent.getChildCount();
int childrenAdded = endChildren - startChildren;
if (childrenAdded == 1) {
View childView = parent.getChildAt(endChildren - 1);
return bind(component, childView, layoutId);
} else {
View[] children = new View[childrenAdded];
for(int i = 0; i < childrenAdded; ++i) {
children[i] = parent.getChildAt(i + startChildren);
}
return bind(component, children, layoutId);
}
}
之后会进入这里:
public abstract ViewDataBinding getDataBinder(DataBindingComponent var1, View var2, int var3);
这是一个接口方法,他的实现是在DataBinderMapperImpl处理的,我们需要注意,这里有两个同样的类名,
我们需要选择build文件夹下动态生成的这个类来做分析。
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: {
// activity_main_0就是我们的根布局的tag,是databinding动态添加的。
if ("layout/activity_main_0".equals(tag)) {
return new ActivityMainBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
}
}
}
return null;
}
接着进入这里:
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
// 这里的9带边你的布局文件中有9个字view。
this(bindingComponent, root, mapBindings(bindingComponent, root, 9, sIncludes, sViewsWithIds));
}
在mapBindings中,这里是内存消耗较大的地方之一,因为他要做大量的解析工作,将控件的所有信息保存到一个object数组里边。
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root, int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
Object[] bindings = new Object[numBindings];
mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
return bindings;
}
最终的控件解析都在这个方法里边:
private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot) {
// 判断view是否已经存在绑定,如果已经绑定,则直接return
ViewDataBinding existingBinding = getBinding(view);
if (existingBinding == null) {
// 获取view的tag标签
Object objTag = view.getTag();
String tag = objTag instanceof String ? (String)objTag : null;
boolean isBound = false;
int indexInIncludes;
int id;
int count;
// 如果tag是根布局,并且是以1ayout开头的tag
if (isRoot && tag != null && tag.startsWith("layout")) {
id = tag.lastIndexOf(95);
if (id > 0 && isNumeric(tag, id + 1)) {
count = parseTagInt(tag, id + 1);
// 将根布局标签对应的view放在bindings数组中
if (bindings[count] == null) {
bindings[count] = view;
}
indexInIncludes = includes == null ? -1 : count;
isBound = true;
} else {
indexInIncludes = -1;
}
}
// 解析非根布局。
else if (tag != null && tag.startsWith("binding_")) {
id = parseTagInt(tag, BINDING_NUMBER_START);
if (bindings[id] == null) {
bindings[id] = view;
}
isBound = true;
indexInIncludes = includes == null ? -1 : id;
} else {
indexInIncludes = -1;
}
if (!isBound) {
id = view.getId();
if (id > 0 && viewsWithIds != null && (count = viewsWithIds.get(id, -1)) >= 0 && bindings[count] == null) {
bindings[count] = view;
}
}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup)view;
count = viewGroup.getChildCount();
int minInclude = 0;
for(int i = 0; i < count; ++i) {
View child = viewGroup.getChildAt(i);
boolean isInclude = false;
if (indexInIncludes >= 0 && child.getTag() instanceof String) {
String childTag = (String)child.getTag();
if (childTag.endsWith("_0") && childTag.startsWith("layout") && childTag.indexOf(47) > 0) {
int includeIndex = findIncludeIndex(childTag, minInclude, includes, indexInIncludes);
if (includeIndex >= 0) {
isInclude = true;
minInclude = includeIndex + 1;
int index = includes.indexes[indexInIncludes][includeIndex];
int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
int lastMatchingIndex = findLastMatching(viewGroup, i);
if (lastMatchingIndex == i) {
bindings[index] = DataBindingUtil.bind(bindingComponent, child, layoutId);
} else {
int includeCount = lastMatchingIndex - i + 1;
View[] included = new View[includeCount];
for(int j = 0; j < includeCount; ++j) {
included[j] = viewGroup.getChildAt(i + j);
}
bindings[index] = DataBindingUtil.bind(bindingComponent, included, layoutId);
i += includeCount - 1;
}
}
}
}
if (!isInclude) {
mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
}
}
}
}
}
对应看此图:
整个流程就对应起来了。 我们没有写findViewById,因为Databinding内部通过自己查找Tag的方式将所有的控件都保存在Object里边了。
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
// Databinding另一个消耗内存的地方。就是这个super.
super(bindingComponent, root, 2
, (android.widget.EditText) bindings[1]
, (android.widget.EditText) bindings[3]
, (android.widget.EditText) bindings[5]
, (android.widget.EditText) bindings[2]
, (android.widget.EditText) bindings[4]
, (android.widget.EditText) bindings[6]
);
// 经过上述步骤,所有的View都被保存下来了。
this.etName1.setTag(null);
this.etName2.setTag(null);
this.etName3.setTag(null);
this.etPwd1.setTag(null);
this.etPwd2.setTag(null);
this.etPwd3.setTag(null);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView7 = (android.widget.TextView) bindings[7];
this.mboundView7.setTag(null);
this.mboundView8 = (android.widget.TextView) bindings[8];
this.mboundView8.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
Databinding里一个消耗内存的地方是在super方法中,这个super的实现是在ViewDataBinding中。这个类里边有静态代码块:
static {
SDK_INT = VERSION.SDK_INT;
BINDING_NUMBER_START = "binding_".length();
USE_CHOREOGRAPHER = SDK_INT >= 16;
CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return (new WeakPropertyListener(viewDataBinding, localFieldId)).getListener();
}
};
CREATE_LIST_LISTENER = new CreateWeakListener() {
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return (new WeakListListener(viewDataBinding, localFieldId)).getListener();
}
};
CREATE_MAP_LISTENER = new CreateWeakListener() {
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return (new WeakMapListener(viewDataBinding, localFieldId)).getListener();
}
};
CREATE_LIVE_DATA_LISTENER = new CreateWeakListener() {
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return (new LiveDataListener(viewDataBinding, localFieldId)).getListener();
}
};
REBIND_NOTIFIER = new CallbackRegistry.NotifierCallback<OnRebindCallback, ViewDataBinding, Void>() {
public void onNotifyCallback(OnRebindCallback callback, ViewDataBinding sender, int mode, Void arg2) {
switch (mode) {
case 1:
if (!callback.onPreBind(sender)) {
sender.mRebindHalted = true;
}
break;
case 2:
callback.onCanceled(sender);
break;
case 3:
callback.onBound(sender);
}
}
};
sReferenceQueue = new ReferenceQueue();
if (VERSION.SDK_INT < 19) {
ROOT_REATTACHED_LISTENER = null;
} else {
ROOT_REATTACHED_LISTENER = new View.OnAttachStateChangeListener() {
@TargetApi(19)
public void onViewAttachedToWindow(View v) {
ViewDataBinding binding = ViewDataBinding.getBinding(v);
// 只要控件状态发生变化,就会运行。这种监控很消耗内存。
binding.mRebindRunnable.run();
v.removeOnAttachStateChangeListener(this);
}
public void onViewDetachedFromWindow(View v) {
}
};
}
}
OnAttachStateChangeListener 会在每次控件状态发生变化就会变化,如果控件非常多,那么这个listener也都会相继执行。
这里边的runnable最终会走到这里:
private void executeBindingsInternal() {
if (this.mIsExecutingPendingBindings) {
this.requestRebind();
} else if (this.hasPendingBindings()) {
this.mIsExecutingPendingBindings = true;
this.mRebindHalted = false;
if (this.mRebindCallbacks != null) {
this.mRebindCallbacks.notifyCallbacks(this, 1, (Object)null);
if (this.mRebindHalted) {
this.mRebindCallbacks.notifyCallbacks(this, 2, (Object)null);
}
}
if (!this.mRebindHalted) {
this.executeBindings();
if (this.mRebindCallbacks != null) {
this.mRebindCallbacks.notifyCallbacks(this, 3, (Object)null);
}
}
this.mIsExecutingPendingBindings = false;
}
}
executeBindings的具体实现是在ActivityMainBindingImpl,其实这里边就是做了最基本的操作,如setText,addTextWatcher等。还需要注意,在这里边,例如EditText,通过View驱动数据,会添加TextWatcher。
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String studentInfoNameFGet = null;
java.lang.String studentInfoPwdFGet = null;
com.derry.databinding_kt.model.StudentInfo studentInfo = mStudentInfo;
androidx.databinding.ObservableField<java.lang.String> studentInfoNameF = null;
androidx.databinding.ObservableField<java.lang.String> studentInfoPwdF = null;
java.lang.String studentInfoName = null;
java.lang.String studentInfoPwd = null;
if ((dirtyFlags & 0xfL) != 0) {
if ((dirtyFlags & 0xdL) != 0) {
if (studentInfo != null) {
// read studentInfo.nameF
studentInfoNameF = studentInfo.getNameF();
}
updateRegistration(0, studentInfoNameF);
if (studentInfoNameF != null) {
// read studentInfo.nameF.get()
studentInfoNameFGet = studentInfoNameF.get();
}
}
if ((dirtyFlags & 0xeL) != 0) {
if (studentInfo != null) {
// read studentInfo.pwdF
studentInfoPwdF = studentInfo.getPwdF();
}
updateRegistration(1, studentInfoPwdF);
if (studentInfoPwdF != null) {
// read studentInfo.pwdF.get()
studentInfoPwdFGet = studentInfoPwdF.get();
}
}
if ((dirtyFlags & 0xcL) != 0) {
if (studentInfo != null) {
// read studentInfo.name
studentInfoName = studentInfo.getName();
// read studentInfo.pwd
studentInfoPwd = studentInfo.getPwd();
}
}
}
// batch finished
if ((dirtyFlags & 0xcL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etName1, studentInfoName);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etPwd1, studentInfoPwd);
}
if ((dirtyFlags & 0xdL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etName2, studentInfoNameFGet);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etName3, studentInfoNameFGet);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView7, studentInfoNameFGet);
}
if ((dirtyFlags & 0x8L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etName3, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etName3androidTextAttrChanged);
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etPwd3, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etPwd3androidTextAttrChanged);
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView7, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView7androidTextAttrChanged);
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView8, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView8androidTextAttrChanged);
}
if ((dirtyFlags & 0xeL) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etPwd2, studentInfoPwdFGet);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etPwd3, studentInfoPwdFGet);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView8, studentInfoPwdFGet);
}
}
总结来看,数据驱动事件其实就是通过各种监听器来实现的,这也是databinding实现的原理之一。Databinding通过注解处理器,扫描布局,生成两个布局文件,来做事情。
附1:DataBinding的由来
附2:DataBinderMapperImpl 与 ActivityMainBindingImpl的关系
DataBinderMapperImpl对应的是xml中的这一段:
ActivityMainBindingImpl对应的是这一段:
这两个类各司其职,一个做DataBinding的Tag解析,一个做我们自己做的xml解析。
测试源码链接:github.com/xingchaozha…
学后检测
一、单选题(每题4分)
1. 关于 Android DataBinding 编译期生成的 XML 解析文件,下列说法正确的是?
A. 只会生成一份布局 XML
B. 每个控件都会生成唯一 tag,用于查找和绑定
C. 只会为 root view 添加 tag
D. 只生成 activity_main-layout.xml
答案:B
解析:
DataBinding 会为每个需要绑定的 view 自动分配唯一 tag,辅助运行时查找和数据绑定,tag 不是仅加在根布局上。
2. DataBinding 内部通过哪种方式将 layout 文件和具体的 Binding 类(如 ActivityMainBindingImpl)关联起来?
A. 通过 findViewById 递归查找
B. 静态哈希表 + view tag
C. 使用 ViewStub 机制
D. LayoutInflater 的 attachToRoot 参数
答案:B
解析:
DataBinderMapperImpl 通过 tag 查找和 layoutId 对应的 binding 实现类,用哈希表+tag定位对应布局和控件。
3. DataBinding 执行数据更新的关键方法是哪个?
A. onCreateView()
B. executeBindings()
C. updateView()
D. setText()
答案:B
解析:
executeBindings() 会被反复调用,将数据模型最新的值刷新到布局视图上,内部通过 TextWatcher/Listener 实现。
4. DataBinding 的 mapBindings 方法的作用是?
A. 把 XML 转换为 Java 代码
B. 映射 tag 并建立 Object 数组与 view 的索引关系
C. 只负责事件绑定
D. 只解析 include 布局
答案:B
解析:
mapBindings 会遍历布局树,把每个控件(含 include)收集到 Object[] 数组,便于后续直接访问和数据更新。
5. 下列关于 DataBinderMapperImpl 和 ActivityMainBindingImpl 的说法,正确的是?
A. DataBinderMapperImpl 主要负责 view 事件处理
B. ActivityMainBindingImpl 只处理 XML 解析
C. DataBinderMapperImpl 做 tag/布局id 到 Binding 类的映射
D. 二者功能完全重复
答案:C
解析:
DataBinderMapperImpl 负责将 tag/layout id 映射到具体 Binding 实现,ActivityMainBindingImpl 负责具体控件和数据绑定实现。
二、多选题(每题5分)
6. 关于 DataBinding 的性能特点,下列哪些说法正确?
A. 初始化阶段内存占用高
B. 运行期间绑定控件不会有额外内存消耗
C. 根布局和所有子控件都可能被 tag 标记
D. 多 EditText 会额外创建 TextWatcher,可能影响性能
答案:A、C、D
解析:
DataBinding 需要预先解析全部控件,内存占用较高;每个有数据驱动的控件会加监听器;tag 覆盖所有绑定控件。
7. DataBinding 的工作流程包含哪些关键步骤?
A. 注解处理器生成中间 XML、Binding 类
B. 运行时通过 tag 识别并绑定 view
C. 事件和数据变更通过监听器(如 TextWatcher)回传
D. 需要手动调用 findViewById 绑定每个 view
答案:A、B、C
解析:
findViewById 被 DataBinding 内部通过 tag 自动管理,不需要手动写。
8. 下列属于 DataBinding 生成的 ActivityMainBindingImpl 作用的是?
A. 保存所有控件的引用
B. 负责将数据 set 到 View
C. 生成 XML 文件
D. 处理 View 的 tag 解析和分配
答案:A、B、D
解析:
ActivityMainBindingImpl 负责将控件与数据绑定,保存所有 View 引用,tag 解析在 mapBindings/构造器实现。
三、判断题(每题3分)
(√)DataBinding 会在布局文件中所有需要绑定的 View 自动生成唯一 tag。
解析:
这是 DataBinding 能自动查找并高效绑定 View 的基础。
(×)DataBinding 一定比传统 findViewById 更省内存和性能更高。
解析:
DataBinding 初始化更耗内存,并不是所有场景都比手动 findViewById 优。
(√)DataBinding 支持数据变更自动驱动界面刷新,无需手动调用 notifyDataSetChanged。
解析:
这是 DataBinding 最大的优势之一,自动追踪数据变化。
(√)多次数据 set,DataBinding 通过标记 dirtyFlags 和批量 executeBindings 提高性能。
解析:
DataBinding 有脏标志和批处理机制,防止重复绑定。
(×)DataBinding 只适合大型复杂页面,小页面用不了。
解析:
小页面完全可以用 DataBinding,不过是否值得要看需求。
四、简答题(每题10分)
1. DataBinding 为什么能不用 findViewById 就能直接访问和更新所有控件?请简述其实现机制。
答案:
DataBinding 编译期通过注解处理器扫描 XML,生成 Binding 类(如 ActivityMainBindingImpl),将所有带数据绑定的控件分配唯一 tag,运行时用 mapBindings 把这些控件统一收集进 Object[] 数组。后续数据变更时直接用数组索引访问控件,实现高效的数据驱动和事件绑定,无需手动 findViewById。
2. DataBinding 内存消耗主要集中在哪些环节?如何优化?
答案:
主要消耗在:
- 1)初始化时遍历和缓存所有控件(mapBindings),生成对象数组。
- 2)对可编辑控件(如 EditText)额外增加监听器(如 TextWatcher)。
- 3)自动为所有绑定控件添加 tag 和 WeakListener(监听器)。
优化思路: - 避免大而全的复杂布局,合理拆分。
- 合理使用 include/reuse 布局。
- 对大批量列表场景避免使用 DataBinding。
五、编程题(每题15分)
请实现一个简单的 DataBinding 手写核心流程(伪代码/Java 均可):
输入:布局文件,含 3 个 View(id 分别为 et_name, et_pwd, tv_result)
要求:数据模型 StudentInfo 包含 name/pwd 字段,模拟数据变动时自动刷新到界面。
参考答案(Java 伪代码):
// 数据模型
class StudentInfo extends BaseObservable {
String name;
String pwd;
// getter/setter + notifyPropertyChanged
}
// 伪 Binding 类
class SimpleBinding {
EditText et_name;
EditText et_pwd;
TextView tv_result;
StudentInfo studentInfo;
void bind(StudentInfo info) {
this.studentInfo = info;
// 初始化控件
et_name.setText(info.getName());
et_pwd.setText(info.getPwd());
tv_result.setText(info.getName() + " " + info.getPwd());
// 数据->UI监听
info.addOnPropertyChangedCallback(() -> {
et_name.setText(info.getName());
et_pwd.setText(info.getPwd());
tv_result.setText(info.getName() + " " + info.getPwd());
});
// UI->数据监听
et_name.addTextChangedListener(s -> info.setName(s.toString()));
et_pwd.addTextChangedListener(s -> info.setPwd(s.toString()));
}
}
解析:
原理是数据变化监听、UI变动回传。DataBinding 的底层本质就是自动生成这些监听和回调代码,替你省去了重复劳动。