In the previous DataBinding principle---Loading of layoutIn this article, we explain the loading process of the layout in DataBinding. Here we continue to the next step, how the data is bound. Here we only introduce one-way data binding, that is, the data changes will be reflected on the control; then we will introduce two-way data binding.
Before analyzing the source code, there must be a concept in mind that the data binding here is implemented based on the observer pattern. Therefore, when reading this part of the source code, you must focus on distinguishing who is the observer and who is the observer. Put this idea in the mind, so that you can grasp the essence of the code.
This article is divided into two small parts, first is the analysis of the data binding process, and then the establishment process of the observer pattern binding relationship.
1. Data binding process
Code analysis, walk. Or post a code for Activity as follows:
class MainActivity : AppCompatActivity() { private val viewModel: SimpleViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { (savedInstanceState) val binding: ActivityMainBinding = (this, .activity_main) = this = viewModel } }
The key to data binding is the line of code = viewModel, and the method it calls is implemented as follows:
public void setViewModel(@Nullable ViewModel) { = ViewModel; synchronized(this) { mDirtyFlags |= 0x4L; } notifyPropertyChanged(); (); }
The notifyPropertyChanged line is actually notifying the observer that the data has changed. But this is not the focus of our attention here (but note that mDirtyFlags here has been modified and has a value). The focus is on the following line (), and its implementation is as follows:
protected void requestRebind() { //Troubleshooting the situation where include tags if (mContainingBinding != null) { (); } else { //We are here in our scene final LifecycleOwner owner = ; if (owner != null) { // If the life cycle is not at least STARTED, return state = ().getCurrentState(); if (!()) { return; // wait until lifecycle owner is started } } synchronized (this) { if (mPendingRebind) { return; } //Set the flag mPendingRebind = true; } if (USE_CHOREOGRAPHER) { //Go here and refresh with vertical synchronization (mFrameCallback); } else { (mRebindRunnable); } } }
First, determine whether it belongs to the include tag (that is, the include tag is included in the layout), if not, go to the else branch, and then judge whether the life cycle is at least STARTED state. If not, data binding will not be performed (data binding will be performed again when the life cycle is restored); if the life cycle requirements are met, continue to judge, first set mPendingRebind (avoid repeated binding of data), and then post a callback using the vertical synchronization refresh mechanism, callback implementation (located in the construction function of ViewDataBinding) as follows:
if (USE_CHOREOGRAPHER) { mChoreographer = (); mFrameCallback = new () { @Override public void doFrame(long frameTimeNanos) { (); } }; } else { mFrameCallback = null; mUIThreadHandler = new Handler(()); }
In fact, the final one is still mRebindRunnable, which is implemented as follows:
private final Runnable mRebindRunnable = new Runnable() { @Override public void run() { synchronized (this) { mPendingRebind = false; } // Handle listeners in weak reference queues to avoid inherent leakage processReferenceQueue(); //If it is Android 4.4 and go here later if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { // Nested so that we don't get a lint warning in IntelliJ //If the View is not attached yet, it will return; data binding will be performed on the attachment later. if (!()) { // Don't execute the pending bindings until the View // is attached again. (ROOT_REATTACHED_LISTENER); (ROOT_REATTACHED_LISTENER); return; } } //Execute data binding. executePendingBindings(); } };
Well, comments are written in the code. First, deal with weak reference queues to avoid memory leaks, and then judge that if the View has not attached, it will return, and then execute data binding on the attached. Finally, executePendingBindings is called to perform data binding.
public void executePendingBindings() { if (mContainingBinding == null) { //No include will go here executeBindingsInternal(); } else { (); } }
In our scenario, we go to executeBindingsInternal, the code is as follows:
private void executeBindingsInternal() { if (mIsExecutingPendingBindings) { requestRebind(); return; } //The method of apt code survival is called here, which is the method in our project. if (!hasPendingBindings()) { return; } mIsExecutingPendingBindings = true; mRebindHalted = false; if (mRebindCallbacks != null) { (this, REBIND, null); // The onRebindListeners will change mPendingHalted if (mRebindHalted) { (this, HALTED, null); } } if (!mRebindHalted) { //Execute binding here executeBindings(); if (mRebindCallbacks != null) { (this, REBOUND, null); } } mIsExecutingPendingBindings = false; }
The above mainly refers to some conditional judgments to avoid repeated execution of binding. The method hasPendingBindings determines whether there is a need to perform data binding. Finally, executeBindings calls to perform data binding; the implementation of hasPendingBindings is located in, which is the class that DataBinding helps us survive. The code is as follows:
// public boolean hasPendingBindings() { synchronized(this) { if (mDirtyFlags != 0) { return true; } } return false; }
We mentioned above that mDirtyFlags' parameter is no longer 0 when the setViewModel method is called, so true will be returned here, that is, the executeBindings method will be executed later, and its implementation is also located in the class. The code is as follows:
@Override protected void executeBindings() { long dirtyFlags = 0; synchronized (this) { //Copy bit identifier, record which data changes. dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } viewModelFirstGetValue = null; <> viewModelSecond = null; <> viewModelFirst = null; viewModelSecondGetValue = null; viewModel = mViewModel; //Judge which data changes according to dirtyFlags. A variable will correspond to a bit in the binary //This bit is not 0, which means that the data has changed, so the corresponding data acquisition logic will be executed. if ((dirtyFlags & 0xfL) != 0) { if ((dirtyFlags & 0xdL) != 0) { if (viewModel != null) { // read viewModelSecond = (); } updateLiveDataRegistration(0, viewModelSecond); if (viewModelSecond != null) { // read () viewModelSecondGetValue = (); } } if ((dirtyFlags & 0xeL) != 0) { if (viewModel != null) { // read viewModelFirst = (); } // Establish an observer binding relationship updateLiveDataRegistration(1, viewModelFirst); if (viewModelFirst != null) { // read () viewModelFirstGetValue = (); } } } // batch finished //Call setText based on the data obtained above, that is, set the corresponding data to the UI to implement //Update the UI logic of data changes. if ((dirtyFlags & 0xeL) != 0) { // api target 1 (, viewModelFirstGetValue); } if ((dirtyFlags & 0xdL) != 0) { // api target 1 (, viewModelSecondGetValue); } } // Listener Stub Implementations // callback impls // dirty flag private long mDirtyFlags = 0xffffffffffffffffL; /* flag mapping flag 0 (0x1L): flag 1 (0x2L): flag 2 (0x3L): viewModel flag 3 (0x4L): null flag mapping end*/ //end
First of all, we will explain that there are generally several variables in our model. In fact, each variable will have a binary bit in DataBinding to identify whether the current data has changed. If it changes, then use dirtyFlags to perform an "And" operation to determine whether the data bit has changed (experience it carefully).
Corresponding to our scenario, flag 0 (0x1L): This flag corresponds to the variable second, and flag 1 (0x2L): corresponds to the variable first, and then use dirtyFlags for operation to know which bit changes.
Wait, the data has been bound, why don’t you see the binding relationship of the observer pattern? In fact, the above code comment has already shown that this line of code updateLiveDataRegistration establishes the binding relationship of the observer pattern.
Finally, I saw the binding of the data. Let’s continue to see how updateLiveDataRegistration establishes the binding relationship of the observer pattern.
2. Establish an observer pattern binding relationship
First, the code of updateLiveDataRegistration is as follows:
// protected boolean updateLiveDataRegistration(int localFieldId, LiveData<?> observable) { mInLiveDataRegisterObserver = true; try { return updateRegistration(localFieldId, observable, CREATE_LIVE_DATA_LISTENER); } finally { mInLiveDataRegisterObserver = false; } }
First, let’s explain the parameter. The first parameter indicates which variable is at which position it is, and the second is LiveData (corresponding to the data variable we define in the ViewModel, that is, the variable to be observed). Here is just a simple forwarding call to updateRegistration. The last parameter is a creator (using the factory method mode), which is implemented as follows:
private static final CreateWeakListener CREATE_LIVE_DATA_LISTENER = new CreateWeakListener() { @Override public WeakListener create( ViewDataBinding viewDataBinding, int localFieldId, ReferenceQueue<ViewDataBinding> referenceQueue ) { return new LiveDataListener(viewDataBinding, localFieldId, referenceQueue) .getListener(); } };
It is to create a LiveDataListener object and call its getListener method, which will return a variable of type WeakListener. Continue with the updateRegistration method above, the code is as follows:
protected boolean updateRegistration(int localFieldId, Object observable, CreateWeakListener listenerCreator) { //Because observable is a livedata object and is not empty, it will not go here. if (observable == null) { return unregisterFrom(localFieldId); } WeakListener listener = mLocalFieldObservers[localFieldId]; //Because it is the first time I enter here, I will enter registerTo if (listener == null) { registerTo(localFieldId, observable, listenerCreator); return true; } if (() == observable) { return false;//nothing to do, same object } unregisterFrom(localFieldId); registerTo(localFieldId, observable, listenerCreator); return true; }
According to the comments in the code, if it is the first entry, it will enter the registerTo method, and its implementation is as follows:
protected void registerTo(int localFieldId, Object observable, CreateWeakListener listenerCreator) { if (observable == null) { return; } WeakListener listener = mLocalFieldObservers[localFieldId]; if (listener == null) { //As mentioned above, the returned WeakListener object is returned here. listener = (this, localFieldId, sReferenceQueue); //Save the object mLocalFieldObservers[localFieldId] = listener; //Set lifeCycle if (mLifecycleOwner != null) { (mLifecycleOwner); } } // Establish an observer binding relationship (observable); }
First, we judge that if the listener is created, the return is a WeakListener object (in fact, there is also a LiveDataListener object in the middle. The mObservable variable of the WeakListener object will hold the LiveDataListener object), and then save it to the mLocalFieldObservers array, which looks like an observer array object. Finally, we call the setTarget method of WeakListener, which is implemented as follows:
public void setTarget(T object) { //The object here is the liveData object passed, and it will be saved to the target below unregister(); mTarget = object; if (mTarget != null) { //mObservable is actually the LiveDataListener object (mTarget); } }
Repeat, the above mTarget is the LiveData object in the ViewModel, and mObservable is actually the LiveDataListener object, so the addListener method of the LiveDataListener is called, and the implementation is as follows:
//ViewDataBinding$LiveDataListener public void addListener(LiveData<?> target) { //target is LiveData LifecycleOwner lifecycleOwner = getLifecycleOwner(); if (lifecycleOwner != null) { //LiveData monitors the lifecycleOwner's lifecycle changes. (lifecycleOwner, this); } }
A very simple logic is to establish a binding relationship of the observer pattern through (lifecycleOwner, this). After the life cycle changes, the onChange method will be called, the code is as follows:
public void onChanged(@Nullable Object o) { ViewDataBinding binder = (); if (binder != null) { (, (), 0); } }
The binder here is actually ActivityMainBindingImpl, and then call handleFieldChange, the code is as follows:
protected void handleFieldChange(int mLocalFieldId, Object object, int fieldId) { if (mInLiveDataRegisterObserver || mInStateFlowRegisterObserver) { // We're in LiveData or StateFlow 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; } //The subclass implementation is called, that is, ActivityMainBindingImpl boolean result = onFieldChange(mLocalFieldId, object, fieldId); if (result) { //Rebound data requestRebind(); } }
First, onFiledChange is called, and its implementation is located in the code where apt survives, as follows:
@Override protected boolean onFieldChange(int localFieldId, Object object, int fieldId) { switch (localFieldId) { case 0 : return onChangeViewModelSecond((<>) object, fieldId); case 1 : return onChangeViewModelFirst((<>) object, fieldId); } return false; } private boolean onChangeViewModelSecond(<> ViewModelSecond, int fieldId) { if (fieldId == BR._all) { synchronized(this) { mDirtyFlags |= 0x1L; } return true; } return false; } private boolean onChangeViewModelFirst(<> ViewModelFirst, int fieldId) { if (fieldId == BR._all) { synchronized(this) { mDirtyFlags |= 0x2L; } return true; } return false; }
According to which data changes, return true or false. If true, requestRebind will be called later. Isn't it the corresponding function analyzed at the beginning? Form a closed loop. This completes the entire observer mode establishment and response process.
This article analyzes the data binding and the establishment process of the observer pattern. If you want to understand the loading of layouts in DataBinding, you can read the previous article DataBinding principle---Loading of layout. The next article will analyze the two-way data binding process.
This is the end of this article about in-depth research on one-way data binding of Android DataBinding. For more related Android DataBinding content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!