Principle of data binding for Android

During the inflate layout, the Activity generates the Binding through DataBindingUtil. From the code point of View, it traverses the contentView to get the View array object, and then generates the corresponding Binding class through the data Binding library, including Views, variables, listeners, etc. The generated classes are located in build/intermediates/classes/debug /... package... / databinding/xxx Java How to generate is not discussed here.

Binding process

  • First, the callback or Handler will be instantiated in the parent class (ViewDataBinding) for subsequent binding operations;
private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16;

if (USE_CHOREOGRAPHER) {
    mChoreographer = Choreographer.getInstance();
    mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            mRebindRunnable.run();
        }
    };
} else {
    mFrameCallback = null;
    mUIThreadHandler = new Handler(Looper.myLooper());
}
  • Next, traverse the layout by calling mapBindings(...) to obtain the array objects containing bound, includes and ID Views, and then assign them to the corresponding View in turn
final Object[] bindings = mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds);
this.mboundView0 = (Android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
  • Then, call invalidateAll () - > requestRebind () - >... > mRebindRunnable.. Run () – execute Runnable
// Used to dynamically rebind Views
private final Runnable mRebindRunnable = new Runnable() {
    @Override
    public void run() {
        synchronized (this) {
            mPendingRebind = false;
        }
        .....
        executePendingBindings();
    }
};
  • Finally, the Runnable will execute to executependingbindings() - >... - > executebindings(), where binding related operations will be performed.
@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;   // Flag of mDirtyFlags variable update
        mDirtyFlags = 0;
    }
    .....
}

Setting variables (data objects)

Ordinary Java bean object

  • First, identify variables through mDirtyFlags (all variables share)
synchronized(this) {
    mDirtyFlags |= 0x1L;
}
  • Then, call notifyPropertyChanged(...) to notify the update (if there is a callback).
public void notifyPropertyChanged(int fieldId) {
    if (mCallbacks != null) {
        mCallbacks.notifyCallbacks(this, fieldId, null);
    }
}
  • Finally, call requestRebind () - > - > executeBindings () to perform the binding operation again, and update the data to Views.
@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    .....
}

Observable object

  • When setting variables, updateRegistration(...) will be called to register the listener of an Observable object
public void setContact(com.connorlin.databinding.model.ObservableContact contact) {
    updateRegistration(0, contact);
    this.mContact = contact;
    synchronized(this) {
        mDirtyFlags |= 0x1L;
    }
    notifyPropertyChanged(BR.contact);
    super.requestRebind();
}
  • Other steps are the same as normal Java bean object

ObservableFields object

  • The previous steps are the same as those of the general Java Bean object
  • Unlike the Observable object, the listener of the Observable object is registered in executeBindings()
@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    ...
    if ((dirtyFlags & 0xfL) != 0) {
        if ((dirtyFlags & 0xdL) != 0) {
            if (contact != null) {
                // read contact.mName
                mNameContact = contact.mName;
            }
            updateRegistration(0, mNameContact);

            if (mNameContact != null) {
                // read contact.mName.get()
                mNameContact1 = mNameContact.get();
            }
        }
        ...
    }
    ...
}

Register Observable object listener

  • Entry updateRegistration(0, contact):
protected boolean updateRegistration(int localFieldId, Observable observable) {
    return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}

private boolean updateRegistration(int localFieldId, Object observable, CreateWeakListener listenerCreator) {
    ...
    // Ensure that the monitoring is not repeated. Remove the monitoring first and then add the observation monitoring
    unregisterFrom(localFieldId);
    registerTo(localFieldId, observable, listenerCreator);
    return true;
}

protected void registerTo(int localFieldId, Object observable, CreateWeakListener listenerCreator) {
    if (observable == null) {
        return;
    }

    // Create an object to mLocalFieldObservers
    WeakListener listener = mLocalFieldObservers[localFieldId];
    if (listener == null) {
        // CREATE_PROPERTY_LISTENER -> create(...)
        listener = listenerCreator.create(this, localFieldId);
        mLocalFieldObservers[localFieldId] = listener;
    }

    // Bind listener to Observable object
    listener.setTarget(observable);
}

Each Observable object will add an observation listener, which is saved in the array mLocalFieldObservers and indexed by localFieldId.

  • CREATE_ PROPERTY_ What is listener?
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
    @Override
    public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
        // Returns the listener (WeakListener) obtained from the WeakPropertyListener instance
        return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
    }
}

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 WeakListener<Observable> getListener() {
        return mListener;
    }

    @Override
    public void addListener(Observable target) {
        // WeakPropertyListener inherits from observable OnPropertyChangedCallback,
        // So this is actually the property listener of the Observable object
        target.addOnPropertyChangedCallback(this);
    }

    ...
}

private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
    private final ObservableReference<T> mObservable;
    protected final int mLocalFieldId;
    private T mTarget;

    ...

    public void setTarget(T object) {
        unregister();
        mTarget = object;
        if (mTarget != null) {
            // mObservable is the above WeakPropertyListener object
            // mTarget is the Observable object bound to the listener
            mObservable.addListener(mTarget);
        }
    }

    ...
}

CREATE_PROPERTY_LISTENER is actually just an interface instance. When registering, it will call its create() method to create a weak reference listener. Its function is to bind the listener to the Observable object. When binding, it will call listener SETTARGET (...) passes the Observable object to the WeakPropertyListener instance, and then the WeakPropertyListener adds OnPropertyChangedCallback to the Observable object.

  • addOnPropertyChangedCallback implementation

addOnPropertyChangedCallback is implemented in BaseObservable. First, a PropertyChangeRegistry object will be instantiated, and a callback callbackregistry will be created to notify the Observable object to rebind the update NotifierCallback. Then add OnPropertyChangedCallback to the callback list of PropertyChangeRegistry

@Override
public synchronized void addOnPropertyChangedCallback(OnPropertyChangedCallback callback) {
    if (mCallbacks == null) {
        mCallbacks = new PropertyChangeRegistry();
    }
    mCallbacks.add(callback);
}

In this way, the listening of the registered Observable object is completed.

Update (rebind) Observable objects

When setting or updating Observable objects, notifyPropertyChanged() or notifyChange() will be called to notify the update. How is it updated?

  • call-back procedure
public void notifyPropertyChanged(int fieldId) {
    // mCallbacks is a PropertyChangeRegistry object, which is instantiated when addOnPropertyChangedCallback
    // If Observable object listening is registered, mCallbacks is not null
    if (mCallbacks != null) {
        mCallbacks.notifyCallbacks(this, fieldId, null);
    }
}

// baseLibrary
private void notifyCallbacks(T sender, int arg, A arg2, int startIndex, int endIndex, long bits) {
    long bitMask = 1L;
    for(int i = startIndex; i < endIndex; ++i) {
        if((bits & bitMask) == 0L) {
            // The mNotifier is created when the PropertyChangeRegistry is instantiated
            // mNotifier is callbackregistry NotifierCallback
            this.mNotifier.onNotifyCallback(this.mCallbacks.get(i), sender, arg, arg2);
        }
        bitMask <<= 1;
    }
}

// PropertyChangeRegistry.NOTIFIER_CALLBACK
public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender, int arg, Void notUsed) {
    // callback is the OnPropertyChangedCallback added for the Observable object, that is, the WeakPropertyListener
    callback.onPropertyChanged(sender, arg);
}

// WeakPropertyListener
public void onPropertyChanged(Observable sender, int propertyId) {
    // The binder is the generated Binding class object
    ViewDataBinding binder = mListener.getBinder();
    ...
    binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}

private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
    // onFieldChange is implemented in the generated Binding class
    boolean result = onFieldChange(mLocalFieldId, object, fieldId);
    if (result) {
        // If the object properties change, they are rebound
        requestRebind();
    }
}

Call the notifyPropertyChanged to the mNotifier callback. The mNotifier notifies onpropertychangedcallback that the property of the observable object has changed, and then transfers it to the ViewDataBinding object (generated Binding class) in onPropertyChanged for processing.

  • Determine whether you need to rebind and execute it, and implement it in the generated Binding class
// Methods in the generated Binding class
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
    // If the variable is not of Observable type or the Bindable annotation is not added, it will not be judged and will directly return false
    switch (localFieldId) {
        case 0 :
            return onChangeContact((com.connorlin.databinding.model.ObservableContact) object, fieldId);
    }
    return false;
}

private boolean onChangeContact(com.connorlin.databinding.model.ObservableContact contact, int fieldId) {
    switch (fieldId) {
        case BR.name: {
            synchronized(this) {
                    mDirtyFlags |= 0x4L;// Judge whether the object changes through mDirtyFlags
            }
            return true;
        }
        ...
    }
    return false;
}

This completes the update process.

The whole registration and update process can be summarized in a flowchart:

event processing

The principle of event handling is very simple. The Binding class will implement the monitoring of View events. The event monitoring of View will be instantiated during construction, and then the event monitoring object will be assigned to the corresponding View during Binding. In this way, the corresponding monitoring will be triggered when clicking.

Here with DataBindingDemo Take the EventActivity section in as an example:

  • Generate the Binding class and implement the event listening of View
public class ActivityEventBinding extends Android.databinding.ViewDataBinding
    implements Android.databinding.generated.callback.OnCheckedChangeListener.Listener,
        Android.databinding.generated.callback.OnClickListener.Listener {
    // Checkbox check listening
    private final Android.widget.CompoundButton.OnCheckedChangeListener mCallback3;
    private final Android.view.View.OnClickListener mCallback2;
    private final Android.view.View.OnClickListener mCallback1;
    // listeners
    private OnClickListenerImpl mAndroidViewViewOnCl;
    ...
    // Listener Stub Implementations
    public static class OnClickListenerImpl implements Android.view.View.OnClickListener{
        private com.connorlin.databinding.handler.EventHandler value;
        public OnClickListenerImpl setValue(com.connorlin.databinding.handler.EventHandler value) {
            this.value = value;
            return value == null ? null : this;
        }
        @Override
        public void onClick(Android.view.View arg0) {
            this.value.onClickFriend(arg0);
        }
    }
    ...
}
  • Event listening of instantiated View
public ActivityEventBinding(Android.databinding.DataBindingComponent bindingComponent, View root) {
    super(bindingComponent, root, 0);
    ...
    // listeners
    mCallback3 = new Android.databinding.generated.callback.OnCheckedChangeListener(this, 3);
    mCallback2 = new Android.databinding.generated.callback.OnClickListener(this, 2);
    mCallback1 = new Android.databinding.generated.callback.OnClickListener(this, 1);
    invalidateAll();
}
  • Bind View event listener in execution binding
@Override
protected void executeBindings() {
    ...
    if ((dirtyFlags & 0x6L) != 0) {
        if (handler != null) {
            // read handler::onClickFriend
            AndroidViewViewOnCli = (((mAndroidViewViewOnCl == null)
                ? (mAndroidViewViewOnCl = new OnClickListenerImpl()) : mAndroidViewViewOnCl).setValue(handler));
        }
    }
    // batch finished
    if ((dirtyFlags & 0x6L) != 0) {
        this.mboundView1.setOnClickListener(AndroidViewViewOnCli);
    }
    if ((dirtyFlags & 0x4L) != 0) {
        this.mboundView2.setOnClickListener(mCallback1);
        this.mboundView3.setOnClickListener(mCallback2);
        Android.databinding.adapters.CompoundButtonBindingAdapter.setListeners(
            this.mboundView4, mCallback3, (Android.databinding.InverseBindingListener)null);
    }
}
  • Trigger event and execute

ViewStub

The principle is similar, except that ViewStubProxy is used to delay binding.

  • Use the viewstub in the layout to instantiate a viewstub proxy object, assign it to the viewstub variable, and associate it with Bingding
public ActivityViewStubBinding(Android.databinding.DataBindingComponent bindingComponent, View root) {
    super(bindingComponent, root, 0);
    final Object[] bindings = mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds);
    ...
    this.viewStub = new Android.databinding.ViewStubProxy((Android.view.ViewStub) bindings[1]);
    this.viewStub.setContainingBinding(this);
    ...
}
  • Instantiating ViewStubProxy will register the inflate listener at the same time
private OnInflateListener mProxyListener = new OnInflateListener() {
    @Override
    public void onInflate(ViewStub stub, View inflated) {
        mRoot = inflated;
        mViewDataBinding = DataBindingUtil.bind(mContainingBinding.mBindingComponent,
                inflated, stub.getLayoutResource());
        mViewStub = null;

        if (mOnInflateListener != null) {
            mOnInflateListener.onInflate(stub, inflated);
            mOnInflateListener = null;
        }
        mContainingBinding.invalidateAll();
        mContainingBinding.forceExecuteBindings();
    }
};

public ViewStubProxy(ViewStub viewStub) {
    mViewStub = viewStub;
    mViewStub.setOnInflateListener(mProxyListener);
}
  • inflate ViewStub
if (!mActivityViewStubBinding.viewStub.isInflated()) {
    mActivityViewStubBinding.viewStub.getViewStub().inflate();
}

When ViewStub infeed, execute mProxyListener, where the Binding of ViewStub will be generated and the main Binding rebinding will be enforced

  • Bind ViewStub
@Override
protected void executeBindings() {
    long dirtyFlags = 0;
    synchronized(this) {
        dirtyFlags = mDirtyFlags;
        mDirtyFlags = 0;
    }
    // batch finished
    if (viewStub.getBinding() != null) {
        viewStub.getBinding().executePendingBindings();
    }
}

This completes the ViewStub binding.

Keywords: Java Android Design Pattern Interview architecture

Added by bubblocity on Wed, 29 Dec 2021 06:03:21 +0200