DataBing原理解析

214 阅读12分钟

简化版的DataBinding整体流程图

XML布局(含<data>标签)
       │
       ▼
【编译期】注解处理器
       │
       ▼
生成xxxBindingImpl类(如ActivityMainBindingImpl)
       │
       ▼
【运行时】setContentView(DataBindingUtil)
       │
       ▼
DataBinderMapperImpl(tag匹配到BindingImpl类)
       │
       ▼
mapBindings(递归收集控件,生成Object[])
       │
       ▼
xxxBindingImpl(保存View引用、生成监听器)
       │
       ▼
executeBindings(脏标志、数据驱动刷新、双向监听)
       │
       ▼
UI自动同步/响应

Databinding生成的xml对应关系

通过Databinding会生成两份xml文件,一份是:

image.png 在这里边,会为每一个view增加一个标签

image.png 我们后边也是通过这个tag来查找view并更新的。 另一份是:

image.png

在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>

整个这一块代码其实是由这一部分生成的:

image.png 那为什么会生成这么多代码呢?因为他需要提取布局里边的所有的控件。

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)。

2. 为什么需要生成两份?

  • 兼容传统 View 加载机制:

    • 传统的布局 inflate 过程不认识 <layout><data> 这些 DataBinding 新增标签。
  • 便于 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处理的,我们需要注意,这里有两个同样的类名,

image.png

我们需要选择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);
                }
            }
        }
    }
}

对应看此图: image.png

整个流程就对应起来了。 我们没有写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的由来

image.png

附2:DataBinderMapperImpl 与 ActivityMainBindingImpl的关系

image.png

DataBinderMapperImpl对应的是xml中的这一段:

image.png

ActivityMainBindingImpl对应的是这一段:

image.png

这两个类各司其职,一个做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 的底层本质就是自动生成这些监听和回调代码,替你省去了重复劳动。