The previous article covered the use of ViewBinding and DataBinding, so let’s look at the relationship between them.

// ViewBinding 
> Task :app:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
> Task :app:dataBindingMergeGenClassesDebug UP-TO-DATE
> Task :app:dataBindingGenBaseClassesDebug UP-TO-DATE

// DataBinding
> Task :databinding:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
> Task :databinding:dataBindingMergeGenClassesDebug UP-TO-DATE
> Task :databinding:dataBindingGenBaseClassesDebug UP-TO-DATE
> Task :databinding:dataBindingTriggerDebug UP-TO-DATE
Copy the code

The ViewBinding belongs to DataBindig. The principles of ViewBinding and DataBinding will be explained next. Examples can be found in the BindingSample

DataBinding contains three blogs:

Use DataBinding (including ViewBinding)

DataBinding (iii) Build process analysis

ViewBinding (ViewBinding

Gradle is configured as follows:

buildFeatures{
    viewBinding = true
}
Copy the code

With the introduction of ViewBinding, the AS plugin will generate xxxbindig. Java code from xxx. XML. In the root layout of the XML file, set tools:viewBindingIgnore=”true” to indicate that the file is ignored. The xxxbinding.java file is not generated

1.1 Generated code

The generated xxxbinding. Java is in this directory: app\build\intermediates\javac\debug\classes\com\ ZBT \ viewBindingTest \databinding

activity_main.xml:

<? The XML version = "1.0" encoding = "utf-8"? > <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/tv_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="noId" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/tv_text" /> </androidx.constraintlayout.widget.ConstraintLayout>Copy the code

The generated ActivityMainBinding. Java:

public final class ActivityMainBinding implements ViewBinding { @NonNull private final ConstraintLayout rootView; @NonNull public final TextView tvText; private ActivityMainBinding(@NonNull ConstraintLayout rootView, @NonNull TextView tvText) { this.rootView = rootView; this.tvText = tvText; } @Override @NonNull public ConstraintLayout getRoot() { return rootView; } @NonNull public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) { return inflate(inflater, null, false); } @NonNull public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent) { // 1. Public View root = inflater.inflate(R.layout.activity_main, Parent, false) if (attachToParent) { parent.addView(root); } return bind(root); } @NonNull public static ActivityMainBinding bind(@NonNull View rootView) { // The body of this method is generated in a  way you would not otherwise write. // This is done to optimize the compiled bytecode for size and performance. // 2. FindViewById (); findViewById (); MissingId: {// I don't know what this is id = r.idv_text; TextView tvText = rootView.findViewById(id); if (tvText == null) { break missingId; } // 3. Pass the constructor to ActivityMainBinding return new ActivityMainBinding((ConstraintLayout) rootView, tvText); } String missingId = rootView.getResources().getResourceName(id); throw new NullPointerException("Missing required view with ID: ".concat(missingId)); }}Copy the code

As you can see, ActivityMainBinding implements the ViewBinding interface, providing that getRoot() returns the root layout of the XML file. The process for obtaining an ActivityMainBinding is as follows:

Graph of TD ActivityMainBinding. Inflate - > ActivityMainBinding. Bind - > ActivityMainBinding constructor

Views with no ID set in the XML file will not be generated in the Binding file.

The binding principle of DataBinding

DataBinding is much more complex than ViewBinding and involves DataBinding.

2.1 Generated code

Take activity_test5.xml in the BindingSample example to view the generated file:

XML, activity_test5-layout. XML, BR, DataBindingMapper, ActivityTest5Binding.

activity_test5.xml

Directory: Project/module/build/intermediates \ incremental \ mergeDebugResources \ stripped dir \ layout \ activity_test5 XML

Activity_test5.xml is a layout file that has been written with the layout tag removed, adding a new tag for each control as follows:

<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="20dp" android:orientation="vertical" android:tag="layout/activity_test5_0"> <TextView style="@style/TvStyle" android:text="model - View : Android: Tag ="binding_1" /> <EditText style="@style/EtStyle" android:layout_marginTop="10dp" android:tag="binding_2" /> <TextView style="@style/TvStyle" Android :layout_marginTop="60dp" Android :text=" event binding "/> <TextView style="@style/TvStyle" Android :tag="binding_3" /> <EditText style="@style/EtStyle" android:layout_marginTop="10dp" android:tag="binding_4" /> </LinearLayout>Copy the code
actiivty_test5-layout.xml

Directory: Project/module/build/intermediates \ data_binding_layout_info_type_merge \ debug \ out \ actiivty_test5 – layout XML

Activity_test5.xml this file is generated from the tag Layout, which extends activity_test5.xml with the following main tags:

  • Contains XML address, package name, and root node information.
  • The model of information
  • Information about the target view to which model points, where the label is boundtagView

Note that tags such as
in XML files are not generated in xxx_XXx-layout. XML, nor are they generated in xxxxxxbinding.java.

<? The XML version = "1.0" encoding = "utf-8" standalone = "yes"? > <Layout directory="layout" filePath="databinding\src\main\res\layout\activity_test5.xml" isBindingData="true" isMerge="false" layout="activity_test5" modulePackage="com.zbt.databinding" rootNodeType="android.widget.LinearLayout"> <Variables name="workBean" declared="true" type="com.zbt.databinding.ObservableWorkBean"> <location endLine="7" endOffset="59" startLine="5" startOffset="8" /> </Variables> <Variables name="eventBinding" declared="true" type="com.zbt.databinding.Test5Activity.EventBinding"> <location endLine="11" endOffset="67" startLine="9" startOffset="8" /> </Variables> <Targets> <Target tag="layout/activity_test5_0" view="LinearLayout"> <Expressions /> <location endLine="49" endOffset="18" startLine="14" startOffset="4" /> </Target> <Target tag="binding_1" view="TextView"> <Expressions> <Expression attribute="android:text" text="workBean.name"> <Location endLine="26" endOffset="42" startLine="26" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="26" endOffset="40" startLine="26" startOffset="28" /> </Expression> </Expressions> <location endLine="26" endOffset="45" startLine="24" startOffset="8" /> </Target> <Target tag="binding_2" view="EditText"> <Expressions> <Expression attribute="android:text"  text="workBean.name"> <Location endLine="31" endOffset="43" startLine="31" startOffset="12" /> <TwoWay>true</TwoWay> <ValueLocation endLine="31" endOffset="41" startLine="31" startOffset="29" /> </Expression> </Expressions> <location endLine="31" endOffset="46" startLine="28" startOffset="8" /> </Target> <Target tag="binding_3" view="TextView"> <Expressions> <Expression attribute="android:onClick" text="()-&gt; eventBinding.onNameClick(workBean)"> <Location endLine="40" endOffset="70" startLine="40" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="40" endOffset="68" startLine="40" startOffset="31" /> </Expression> <Expression attribute="android:text" text="workBean.name"> <Location endLine="41" endOffset="42" startLine="41" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="41" endOffset="40" startLine="41" startOffset="28" />  </Expression> </Expressions> <location endLine="41" endOffset="45" startLine="38" startOffset="8" /> </Target> <Target tag="binding_4" view="EditText"> <Expressions> <Expression attribute="android:afterTextChanged" text="eventBinding::onAfterTextChanged"> <Location endLine="46" endOffset="73" startLine="46" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="46" endOffset="71" startLine="46" startOffset="40" /> </Expression> <Expression attribute="android:hint" text="workBean.name"> <Location endLine="47" endOffset="42" startLine="47" startOffset="12" /> <TwoWay>false</TwoWay> <ValueLocation endLine="47" endOffset="40" startLine="47" startOffset="28" />  </Expression> </Expressions> <location endLine="47" endOffset="45" startLine="43" startOffset="8" /> </Target> </Targets> </Layout>Copy the code
BR

Directory: Project/module/build/generated \ source \ kapt \ debug \ com \ ZBT \ databinding \ BR Java

This directory mainly generates ids based on

and fields annotated with @bindable, similar to id ResId generated by resources.

public class BR { public static final int _all = 0; public static final int activity = 1; public static final int employeeBean = 4; public static final int eventBinding = 5; ... public static final int workBean = 13; public static final int workContent = 14; public static final int workName = 15; }Copy the code
DataBindingmapperImpl

Directory: Project/module/build/generated \ source \ kapt \ debug \ com \ ZBT \ databinding \ DataBindingmapperImpl Java

This file mainly stores the XML corresponding to the tag binding and the IDS and corresponding fields in the BR

public class DataBinderMapperImpl extends DataBinderMapper { private static final int LAYOUT_ACTIVITYTEST1 = 1; private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(13); The static {INTERNAL_LAYOUT_ID_LOOKUP. Put (com. ZBT. Databinding. R.l ayout. Activity_test1,... INTERNAL_LAYOUT_ID_LOOKUP.put(com.zbt.databinding.R.layout.view_stub, LAYOUT_VIEWSTUB); } @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_ACTIVITYTEST1: { if ("layout/activity_test1_0".equals(tag)) { return new ActivityTest1BindingImpl(component, view); } throw new IllegalArgumentException("The tag for activity_test1 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>(16); static { sKeys.put(0, "_all"); ... } } private static class InnerLayoutIdLookup { static final HashMap<String, Integer> sKeys = new HashMap<String, Integer>(13); static { sKeys.put("layout/activity_test1_0", com.zbt.databinding.R.layout.activity_test1); ... }}}Copy the code
Activity5TestBinding

Two classes are generated in kotlin’s coded project: ActivityTest5Binding, which is the Abstract class, and ActivityTest5BindingImpl, which is a subclass of the former.

Directory:

  1. Project\module\build\generated\data_binding_base_class_source_out\debug\out\com\zbt\databinding\databinding\ActivityTest 5Binding.java
  2. Project\module\build\generated\source\kapt\debug\com\zbt\databinding\databinding\ActivityTest5BindingImpl.java

Simple analysis

// ViewDataBinding public abstract class ViewDataBinding extends BaseObservable implements ViewBinding {} // ActivityTest5Binding public abstract class ActivityTest5Binding extends ViewDataBinding { @Bindable protected ObservableWorkBean mWorkBean; @Bindable protected Test5Activity.EventBinding mEventBinding; protected ActivityTest5Binding(Object _bindingComponent, View _root, int _localFieldCount) { super(_bindingComponent, _root, _localFieldCount); } public abstract void setWorkBean(@Nullable ObservableWorkBean workBean); @Nullable public ObservableWorkBean getWorkBean() { return mWorkBean; }... } // ActivityTest5BindingImpl public class ActivityTest5BindingImpl extends ActivityTest5Binding implements com.zbt.databinding.generated.callback.OnClickListener.Listener { @NonNull private final android.widget.LinearLayout mboundView0; @NonNull private final android.widget.TextView mboundView1; @NonNull private final android.widget.EditText mboundView2; @NonNull private final android.widget.TextView mboundView3; @NonNull private final android.widget.EditText mboundView4; public ActivityTest5BindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) { this(bindingComponent, root, mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds)); } private ActivityTest5BindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) { super(bindingComponent, root, 1 ); // Set the View in the same way as in the ViewBinding constructor. If the View sets the Id, it will not be assigned here. In ActivityTest5Binding constructor for the assignment of the View this. MboundView0 = (android. The widget. The LinearLayout) bindings [0]. this.mboundView0.setTag(null); this.mboundView1 = (android.widget.TextView) bindings[1]; this.mboundView1.setTag(null); this.mboundView2 = (android.widget.EditText) bindings[2]; this.mboundView2.setTag(null); this.mboundView3 = (android.widget.TextView) bindings[3]; this.mboundView3.setTag(null); this.mboundView4 = (android.widget.EditText) bindings[4]; this.mboundView4.setTag(null); setRootTag(root); // listeners mCallback1 = new com.zbt.databinding.generated.callback.OnClickListener(this, 1); invalidateAll(); }}Copy the code

You can see that ViewDataBinding implements the ViewBinding interface, so ViewBinding is a subset of DataBinding. The XxxBindingImpl file mainly generates the corresponding View and carries on the data binding to the View.

The mapBindings() method gets the View in the layout

private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds, Boolean isRoot) {...... Object objTag = view.getTag(); final String tag = (objTag instanceof String) ? (String) objTag : null; boolean isBound = false; View if (isRoot && tag! = null && tag.startsWith("layout")) { final int underscoreIndex = tag.lastIndexOf('_'); if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) { final int index = parseTagInt(tag, underscoreIndex + 1); if (bindings[index] == null) { bindings[index] = view; } indexInIncludes = includes == null ? -1 : index; isBound = true; } else { indexInIncludes = -1; }}... If (View instanceof ViewGroup) {final ViewGroup ViewGroup = (ViewGroup) View; final int count = viewGroup.getChildCount(); int minInclude = 0; for (int i = 0; i < count; i++) { final View child = viewGroup.getChildAt(i); Bindings (bindings, bindings, bindings, bindings, bindings) {}}Copy the code
2.2 Binding Process

Different types of components use different binding methods, which can be divided into the following two types:

// Activity DataBindingUtil.setContentView(Activity activity, View databindingutil.inflate (LayoutInflater inflater, int layoutId, ViewGroup parent, boolean attachToParent)Copy the code

Take a look at the call flow:

graph TD
DataBindingUtil.setContentView --> DataBindingUtil.setContentView1 --> DataBindingUtil.bindToAddedViews --> DataBingdingUtil.bind --> DataBinderMapper.getDataBinder

DataBindingUtil.inflate --> DataBindingUtil.inflate1 -->temp{useChildren}
temp --> |Yes| DataBindingUtil.bindToAddedViews
temp --> |No| DataBingdingUtil.bind

With the files generated in the previous section, the binding process is clear.

2.3 Setting Data

The process from setting data to refreshing data is as follows:

graph TD
ActivityTest5BindingImpl.setWorkBean --> ViewDataBinding.requestBind --> temp{api>=16}
temp --> |Yes| mChoreographer.postFrameCallback --> mRebindRunnable.run
temp --> |No| mUIThreadHandler.post --> mRebindRunnable.run
mRebindRunnable.run --> ViewDataBinding.executePendingBindings --> ViewDataBinding.executeBindings

Take a look at the code in turn:

public void setWorkBean(@Nullable com.zbt.databinding.ObservableWorkBean WorkBean) {
    this.mWorkBean = WorkBean;
    synchronized (this) {
        mDirtyFlags |= 0x4L;
    }
    notifyPropertyChanged(BR.workBean);
    super.requestRebind();
}
Copy the code

MDirtyFlags | = 0 x4l is symbol which property changed, notifyPropertyChanged (BR) workBean) inform the corresponding model change.

Protected void requestRebind() {// mContentingBinding is the parent DataBinding if (mContainingBinding! = null) { mContainingBinding.requestRebind(); } else {lifecycle is set. If LifecycleOwner is set, lifecycle is not refreshed. Execute executePendingBindings() refresh final LifecycleOwner owner = this.mlifecycleOwner; if (owner ! = null) { Lifecycle.State state = owner.getLifecycle().getCurrentState(); if (! state.isAtLeast(Lifecycle.State.STARTED)) { return; Synchronized (this) {if (mPendingRebind) {return; synchronized (this) {if (mPendingRebind) {return; synchronized (this) {if (mPendingRebind) {return; } mPendingRebind = true; } / / choreographer API > = 16 is used if (USE_CHOREOGRAPHER) {mChoreographer. PostFrameCallback (mFrameCallback); } else { mUIThreadHandler.post(mRebindRunnable); }}}Copy the code

If API >=16s, an mFrameCallback is sent to mChoreographer, otherwise an mRebindRunnable is sent directly to the UI thread, and both will end up calling mRebindrunnable.run ().

private final Runnable mRebindRunnable = new Runnable() {
    @Override
    public void run() {
        synchronized (this) {
            mPendingRebind = false;
        }
        processReferenceQueue();

        if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
            // Nested so that we don't get a lint warning in IntelliJ
            if (!mRoot.isAttachedToWindow()) {
                // Don't execute the pending bindings until the View
                // is attached again.
                mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                return;
            }
        }
        executePendingBindings();
    }
};
Copy the code

Reset mPendingRebind to false. If API >= 19, check if mRoot attaches to window, if not, Add OnAttachStateChangeListener is of the view, when the root Viewattach to contentView callback onViewAttachedToWindow, ExecutePendingBindings () is executed and the executeBindingsInternal() method is called.

Private void executeBindingsInternal() {// Executing executeBindingsInternal Executing assignment, Is to refresh the if (mIsExecutingPendingBindings) {requestRebind (); return; } // hasPendingBindings() == false hasPendingBindings()) { return; } mIsExecutingPendingBindings = true; mRebindHalted = false; if (mRebindCallbacks ! = null) { mRebindCallbacks.notifyCallbacks(this, REBIND, null); // The onRebindListeners will change mPendingHalted if (mRebindHalted) { mRebindCallbacks.notifyCallbacks(this, HALTED, null); } } if (! mRebindHalted) { executeBindings(); if (mRebindCallbacks ! = null) { mRebindCallbacks.notifyCallbacks(this, REBOUND, null); } } mIsExecutingPendingBindings = false; }Copy the code

MRebindCallbacks is a binding that registers addOnRebindCallback(object: OnRebindCallback

() to listen for every data change. Finally, execute executeBindbings() to complete the view’s data setting.

@Override protected void executeBindings() { long dirtyFlags = 0; synchronized (this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } com.zbt.databinding.Test5Activity.EventBinding eventBinding = mEventBinding; androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged afterTextChanged = null; java.lang.String workBeanNameGet = null; androidx.databinding.ObservableField<java.lang.String> workBeanName = null; com.zbt.databinding.ObservableWorkBean workBean = mWorkBean; if ((dirtyFlags & 0xaL) ! = 0) { if (eventBinding ! = null) { // read eventBinding::onAfterTextChanged afterTextChanged = (((mAfterTextChanged == null) ? (mAfterTextChanged = new AfterTextChangedImpl()) : mAfterTextChanged).setValue(eventBinding)); } } if ((dirtyFlags & 0xdL) ! = 0) { if (workBean ! = null) { // read workBean.name workBeanName = workBean.getName(); } updateRegistration(0, workBeanName); if (workBeanName ! = null) { // read workBean.name.get() workBeanNameGet = workBeanName.get(); } } // batch finished if ((dirtyFlags & 0xdL) ! = 0) { // api target 1 androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, workBeanNameGet); this.mboundView4.setHint(workBeanNameGet); androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvBinding3, workBeanNameGet); } if ((dirtyFlags & 0x8L) ! = 0) { // api target 1 androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, null, null, null, mboundView2androidTextAttrChanged); this.tvBinding3.setOnClickListener(mCallback1); }}Copy the code

This code is mainly to determine whether the dirtyFlags has changed, which corresponding field changes, the corresponding change of the field will be assigned. This field is described in the setWorkBean. The TextView. SetText (), by TextViewBindingAdapter. SetText () for the assignment. In Databinding – Adapters, there is a BindingAdapter for the basic View transformation, for example: TextViewBindingAdapter, ImageViewBindingAdapter, the common methods are all rewrapped with the @bindingAdapter annotation. These adapters are used to convert the values of individual views in XxxXxxBindingImpl.

The above process is the change process of model -> view when setting data, then there are changes in data, model -> view change process.

ObservableField, ObservableBoolean, etc. UpdateRegistration (0, workBeanName) is used to register data change listeners for the corresponding field change in executeBinding in XxxXxxBindingImpl. Java as follows:

graph TD
XxxBindingImpl.updateRegistration --> ViewDataBinding.updateRegistration --> ViewDataBinding.registerTo --> WeakListener.setTarget --> mObservable.addListener --> WeakPropertyListener.addListener -->  target.addOnPropertyChangedCallback

ObservableField.set --> BaseObservable.notifyChange --> CallbackRegistry.notifyCallbacks --> 
WeakPropertyListener.onPropertyChanged --> ViewDataBinding.handleFieldChange --> ViewDataBinding.requestRebind

Through the flow chart of the above, you can see that field ObservableField eventually added addOnPropertyChangedCallback, the callback is WeakPropertyListener instance. When ObservableField calls set(value), it finally invokes its own registered callback, which in turn invokes requestRebind to refresh the data. Here’s the code:

protected boolean updateRegistration(int localFieldId, Observable observable) {
    return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
Copy the code

The CREATE_PROPERTY_LISTENER:

private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() { @Override public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) { return new WeakPropertyListener(viewDataBinding, localFieldId).getListener(); }};Copy the code

Create CreateWeakListener and WeakListener

protected void registerTo(int localFieldId, Object observable, CreateWeakListener listenerCreator) { if (observable == null) { return; } WeakListener listener = mLocalFieldObservers[localFieldId]; if (listener == null) { listener = listenerCreator.create(this, localFieldId); mLocalFieldObservers[localFieldId] = listener; if (mLifecycleOwner ! = null) { listener.setLifecycleOwner(mLifecycleOwner); } } listener.setTarget(observable); }Copy the code

Observable is ObservableField

private static class WeakListener<T> extends WeakReference<ViewDataBinding> { public void setTarget(T object) { unregister(); mTarget = object; if (mTarget ! = null) { mObservable.addListener(mTarget); }}}Copy the code

Where mObservable is the WeakPropertyListener passed in the constructor

private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
        implements ObservableReference<Observable> {
    final WeakListener<Observable> mListener;

    public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
        mListener = new WeakListener<Observable>(binder, localFieldId, this);
    }

    @Override
    public void addListener(Observable target) {
        target.addOnPropertyChangedCallback(this);
    }

    @Override
    public void onPropertyChanged(Observable sender, int propertyId) {
        ViewDataBinding binder = mListener.getBinder();
        if (binder == null) {
            return;
        }
        Observable obj = mListener.getTarget();
        if (obj != sender) {
            return; // notification from the wrong object?
        }
        binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
    }
}
Copy the code

AddListener (observables target) of the target is before the incoming WeakWeakListener ObservableField, this completes the addOnPropertyChangedCallback.

Take a look at the registration and notification process

public class BaseObservable implements Observable { private transient PropertyChangeRegistry mCallbacks; ... // 1. Register callback listener, Create PropertyChangeRegistry @ Override public void addOnPropertyChangedCallback (@ NonNull OnPropertyChangedCallback callback)  { synchronized (this) { if (mCallbacks == null) { mCallbacks = new PropertyChangeRegistry(); } } mCallbacks.add(callback); }... public void notifyChange() { synchronized (this) { if (mCallbacks == null) { return; } } mCallbacks.notifyCallbacks(this, 0, null); }... } public class PropertyChangeRegistry extends CallbackRegistry<Observable.OnPropertyChangedCallback, Observable, Void> { // 0. Generated static variable, when PropertyChangeRegistry is created, To be assigned to the parent class private static final CallbackRegistry. NotifierCallback < observables. OnPropertyChangedCallback observables, Void> NOTIFIER_CALLBACK = new CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void>() { @Override public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender, int arg, Void notUsed) { // 4. In the final call WeakPropertyListener method (process) based on the data change refresh the View callback. OnPropertyChanged (sender, arg); }}; public PropertyChangeRegistry() { super(NOTIFIER_CALLBACK); }... } public class CallbackRegistry<C, T, A> implements Cloneable { private final NotifierCallback<C, T, A> mNotifier; public CallbackRegistry(NotifierCallback<C, T, A> notifier) { mNotifier = notifier; } // 2. ObservableField set(value) Call notifyCallback public synchronized void notifyCallbacks(T sender, int arg, A arg2) {mNotificationLevel++; notifyRecurse(sender, arg, arg2); ... } private void notifyRecurse(T sender, int arg, A arg2) { notifyRemainder(sender, arg, arg2, remainderIndex); ... } private void notifyRemainder(T sender, int arg, A arg2, int remainderIndex) { if (remainderIndex < 0) { notifyFirst64(sender, arg, arg2); } else {... notifyCallbacks(sender, arg, arg2, startIndex, endIndex, bits); } } private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex, final int endIndex, final long bits) { long bitMask = 1; for (int i = startIndex; i < endIndex; I++) {if ((bits & bitMask) == 0) {// 3.mNotifier is the variable NOTIFIER_CALLBACK, And then finally to invoke the callback. OnPropertyChanged (sender, arg); mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2); } bitMask <<= 1; }}... public abstract static class NotifierCallback<C, T, A> { public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2); }}Copy the code

The registration notification process is 0, 1, 2, 3, and 4 steps above.

Let’s see, after receiving the notification, execute the logic of change

private void handleFieldChange(int mLocalFieldId, Object object, Int fieldId) {/ / set the LiveDataRegisterObserver if (mInLiveDataRegisterObserver) {/ / We 'r e in LiveData registration, which always results in a field change // that we can ignore. The value will be read immediately after anyway, so // there is no need to be dirty. return; } // Check whether the file has changed using fiedId in updateRegistration(0, XXX); Boolean result = onFieldChange(mLocalFieldId, object, fieldId); if (result) { requestRebind(); } } @Override protected boolean onFieldChange(int localFieldId, Object object, int fieldId) { switch (localFieldId) { case 0 : return onChangeWorkBeanName((androidx.databinding.ObservableField<java.lang.String>) object, fieldId); } return false; } private boolean onChangeWorkBeanName(androidx.databinding.ObservableField<java.lang.String> WorkBeanName, Int fieldId) {if (fieldId = = BR. _all) {BR. _all of 0 synchronized (this) {mDirtyFlags | = 0 x1l; } return true; } return false; }Copy the code

This makes the process of notifying ViewB when there is a change in the Model clear.

The View notifies the Model of changes

This is typically the case for EditText, which notifies Model when EditText changes, as follows

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="workBean"
            type="com.zbt.databinding.ObservableWorkBean" />

        <variable
            name="eventBinding"
            type="com.zbt.databinding.Test5Activity.EventBinding" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="20dp"
        android:orientation="vertical">

        <EditText
            style="@style/EtStyle"
            android:layout_marginTop="10dp"
            android:text="@={workBean.name}" />

        <EditText
            style="@style/EtStyle"
            android:layout_marginTop="10dp"
            android:afterTextChanged="@{eventBinding::onAfterTextChanged}"
            android:hint="@{workBean.name}" />

    </LinearLayout>
</layout>
Copy the code

There are two EditTexts in the layout. The difference is that the method of listening is different, one is on Android :afterTextChanged, the other is on Android: Text

@Override protected void executeBindings() { if ((dirtyFlags & 0x8L) ! = 0) { // api target 1 androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, null, null, null, mboundView2androidTextAttrChanged); this.tvBinding3.setOnClickListener(mCallback1); } if ((dirtyFlags & 0xaL) ! = 0) { // api target 1 androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView4, null, null, afterTextChanged, null); }}Copy the code

You can see that the setTextWatcher in the adapter class that invoked DataBinding sets the observer. The variable mboundView2androidTextAttrChanged is an interface implementation class, as follows:

private androidx.databinding.InverseBindingListener mboundView2androidTextAttrChanged = new androidx.databinding.InverseBindingListener() { @Override public void onChange() { java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView2); ... If (workBeanJavaLangObjectNull) {... if (workBeanNameJavaLangObjectNull) { workBeanName.set(((java.lang.String) (callbackArg_0))); }}}};Copy the code

Set the TextViewBindingAdapter. SetTextWatcher ()

@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged", "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false) public static void setTextWatcher(TextView view, final BeforeTextChanged before, final OnTextChanged on, final AfterTextChanged after, final InverseBindingListener textAttrChanged) { final TextWatcher newValue; if (before == null && after == null && on == null && textAttrChanged == null) { newValue = null; Else {newValue = new TextWatcher() {... @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (on ! = null) { on.onTextChanged(s, start, before, count); } if (textAttrChanged ! = null) { textAttrChanged.onChange(); }}}; }... }Copy the code

Among these is textAttrChanged mboundView2androidTextAttrChanged the above variables. After the text changes, notify Model of the change. AfterTextChanged implements the internal interface in the TextViewBindingAdapter

public static class AfterTextChangedImpl implements androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged{ private com.zbt.databinding.Test5Activity.EventBinding value; public AfterTextChangedImpl setValue(com.zbt.databinding.Test5Activity.EventBinding value) { this.value = value; return value == null ? null : this; } @Override public void afterTextChanged(android.text.Editable arg0) { this.value.onAfterTextChanged(arg0); }}Copy the code

View change notification model assignment is also implemented

For a View or ViewGroup, if it has a data change similar to EditText, or onAnimationStart in a ViewGroup, the View -> Model process can be implemented.

Third, summary

That’s it, the files generated by ViewBinding and DataBinding, and the whole process of bidirectional binding in DataBinding. The process of data binding is quite complicated. You can see the whole binding process with the flow chart.

4. Refer to the article

DataBinding source code parsing