primers

Data binding has been in the forefront for a few years now, with popular front-end frameworks such as Angular.js, react. js, and vue.js providing this capability.

Data binding is simply a mechanism that binds data in code to XML (UI) so that both sides can manipulate the data and automatically refresh the data as it changes.

Data binding is divided into one-way binding and bidirectional binding.

With unidirectional binding, the flow of data is unilateral and can only flow from code to THE UI. The data flow of bi-directional binding is bi-directional, and the data on the UI can be refreshed as the data in the business code changes; As the user interacts with the UI to edit the data, the changes to the data are automatically updated to the data in the business code.



Android DataBinding Framework

At Google IO in 2015, the Android UI Toolkit team announced the DataBinding framework, which introduced DataBinding to Android development. At that time, one-way binding was only supported and needed to be introduced as a third-party dependency. A year later, two-way binding was also supported. With the Android Gradle Plugin(1.5.0+), data binding is available by adding just three lines to the Gradle configuration file.

Review images

Data binding framework

Advantages of using data binding

  1. Reduce the amount of glue code (findViewById, setOnClickListener) that needs to be written manually.

  2. High performance (most of the work is done at compile time, avoiding reflection at runtime);

  3. Flexibility in usage (you can use expressions to perform certain logical operations in the layout);

  4. IDE support (syntax highlighting, autocomplete, syntax error marking).

Let’s take a simple example

Requirements: There are two controls on the interface, EditText for getting user input and TextView for displaying user input.

Traditional implementation: To do this the traditional way, we need to define a layout, set up the two controls, reference the layout in our code, find the two controls, add listeners to the EditText, take the input when it changes, and update it to the TextView.

With data binding, our code would look like this:

Review images

Review images

Review images

As you can see, with data binding, the logical structure of our code becomes clearer and the hand-written glue code is simplified (generated for us by the data binding framework). The data binding framework helps us listen for changes in the control and synchronously updates the data to the control.

Use of data binding

Layout file modification

The < Layout > tag is the root node of the data Binding layout file, indicating that this is a data Binding layout. After modification, the data Binding framework will generate the corresponding *Binding class, such as content_main. XML will generate the ContentMainBinding class. Capitalize the word, remove the underscore, and add a Binding at the end.

Declaration of data and auxiliary class imports

Declare data by adding a tag inside the < Layout > tag. Adding a class attribute to the tag changes the name of the generated *Binding class, such as using to change it to ContentMain.

Data tags declare variables internally via the

tag, import helper classes via the

tag, and specify an alias using the alias attribute to avoid name conflicts.

Review images

Use of data binding

After a variable is declared, it can be used in the layout in a similar way to using Java. When an expression uses an attribute within an object, it attempts direct calls, getters, and observableField.get ().

It is worth mentioning that expressions are supported within data binding and can be used to perform some basic logical operations.

Common operations are as follows:

  1. Mathematical calculators: +, -, *, /, %

  2. String concatenation: +

  3. Logical operators, &&, | |

  4. Comparison operators: ==, >, <, >=, <=

  5. A function call

  6. Type conversion

  7. Data access [], which is supported by operations on container classes

  8. Null merge operator:?? The merge operator uses the left side when the variable is not empty and the right side when the variable is not empty, such as data?? data.defaultVal

event

Strictly speaking, event binding is also a type of data binding. The android:onClick=”onBtnClick” that we used to do inside the layout can be considered a data binding. But using a data binding framework allows us to do more.

Data binding allows you to pass in a variable and call a method on that variable to handle the event. Compared to the original way, data binding allows you to decouple the logic of handling the event from the class associated with the layout, making it easy to replace different processing logic.

We can also use expressions to execute code directly within the layout without switching back to Java code. This feature allows us to bring uI-specific logic together for logic that requires no external processing but is only related within the layout.

Review images

Another feature of the data binding framework, and the relevant data before operation, and will check to see if the variable is empty, if there is no incoming corresponding variables, is empty, or control on the layout of operation does not perform, therefore, if the above example, we have no incoming corresponding presenter object, click on the button will not trigger a Crash.

Also, because compile-time checks are carried out, if the corresponding method is not implemented on the corresponding data type, or if the method signature is not correct (the argument type should be View), an error will be reported at compile time, thus ensuring the stability of the code.

The data model

While data binding supports POJOs (Pure Old Java Objects), data updates to POJOs do not update the UI synchronously. To implement automatic updates, you can select:

  1. Inherits from BaseObservable, annotates getters with @bindable annotations, and implements domain change notifications in setters.

  2. If the data class does not inherit from a BaseObservable, change notification can be implemented using PropertyChangeRegistry.

  3. Finally, the ObservableField

    is used to access data through ObservableField

    get and set method calls. ObservableField

    is a generic class. For the base type, there are corresponding ObservableInt, ObservableLong, ObservableShort, etc. ObservableArrayList and ObservableArrayMap are ObservableArrayMap, ObservableArrayMap, ObservableArrayMap, and ObservableArrayList.


In terms of use, the third method is more intuitive and convenient, requires less manual intervention and is less prone to mistakes. Therefore, it is recommended to use it.

There’s a lot more you can say about using Data Binding, such as resource references, variable dynamic Settings, support for Lambda expressions, and so on. For more information on how to use Data Binding, see the Data Binding guide in Resources.

How data binding works

How does data binding work? I modified the layout file slightly, added a few controls, used expressions, and ended up with the code: portal

Initialization of data binding related classes

First we need to find a breakthrough point, the most obvious point is ContentMainBinding. Inflate, this class is generated by the data binding framework, The generated files in the build/intermediates/classes/debug / < package_name > / databinding/directory.

Review images

The implementation of the inflate method calls another inflate method and, after a few twists and turns, ends up calling the contentMainbinding.bind method.

Review images

This method first checks if the view is a data-binding related layout. If it is not, it raises an exception. If it is, it instantiates a ContentMainBinding.

How is the ContentMainBinding instantiated? Take a look at the generated code.

Review images

The constructor calls mapBindings (Bindings) to find all the views in root. The number 8 means there are 8 views in the layout. Then it passes in sIncludes and sViewsWithIds. The latter is the index that contains the ID in the layout.

These two parameters are static variables. See how they are initialized:

Review images

Since the layout in the Demo does not contain include, the value of sIncludes is null, and there is a control with id R.i.Fulname in the layout, so it is added to sViewsWithIds. 7 represents its bindings index.

Back to the constructor, all views found by mapBindings are placed in this array, and they can be retrieved one by one by generating code and converted into the corresponding data type. Controls with ID will use ID as variable name, controls without ID will use ID as variable name. Assign values in sequence as mboundView + number. Then associate the Binding with root (by setting the Binding to the tag of the rootView).

An OnClickListener is also instantiated to bind the event response.

The method of mapBindings is implemented in the ViewDataBinding class. It is mainly used to find all views in the root bindings and place them in the corresponding bindings index. When the layout is processed, data binding generates auxiliary information in the view’s tag, which can be parsed to determine the corresponding index. So, to avoid accidentally manipulating the view’s tag after the inflate layout file and interfering with parsing, try to use data binding to get the view behind the inflate. The layout of the processed pieces as follows, generate location for app/build/intermediates/data – binding – layout – out / < build -type > / layout/directory.

Review images

MapBindings method is quite long, which has been processed for different situations. Here, the source code is not posted, and interested readers can read by themselves. In addition, although this method appears to use recursion, it actually implements the traversal of all controls under root in this way. Therefore, the time complexity of the whole method is O(n), and the overall performance of the method is better than that of findViewById.

The instantiated OnClickListener takes two arguments, one OnClickListener.Listener, and a ContentMainBinding implements this interface, so the first argument is passed a ContentMainBinding. The other is the sourceId that identifies the control this listener is used for. What this OnClickListener does is simply pass the click event, with the sourceId attached, back to the _internalCallbackOnClick handler in ContentMainBinding. In the end, all of our layout related operation logic will eventually be condensed into the ContentMainBinding class.

Review images

Fullname.settext (firstName +·+ lastName). SourceId is not used because there is only one such processing logic in the layout. If there are more than 2 bits of logic, a switch block is generated to execute different instructions via sourceId. As you can also see from the implementation, the code generated by the framework uses local variables to hold member variables to ensure thread-safe access to the variables. Similarly, a null check is performed before a control is accessed to avoid null pointer errors. This is one of the benefits of using data binding: null-checking in the framework’s automatically generated code avoids the null-pointer errors that manual coding tends to cause.

The constructor creates a listener, but does not set it to the corresponding control.

Rebind mechanism for data binding

At the end of the constructor, the method invalidateAll is called.

Review images

The invalidateAll method is simply implemented by marking the dirty bit mDirtyFlags as 0x10L. In binary representation, the 5th bit is 1. This dirty bit is the value of a long, which is a maximum of 64 bits available. Since the mDirtyFlags variable is a member variable and multiple writes are made to it, all writes to it are synchronous. After updating this value, the requestRebind method is called to requestRebind.

This method is implemented in the ViewDataBinding base class of ContentMainBinding.

Review images

If rebind has not been requested before, mPendingRebind will be set to true, API level 16 and above, and a mFrameCallback will be sent to mChoreographer to perform rebind when the system refreshes the interface (doFrame). Below API level 16, an mRebindRunnable task is posted to the UI thread. MFrameCallback actually calls the run method of mRebindRunnable internally, so the two tasks do nothing different except call timing.

If the mPendingRebind value is true, requestRebind will return to the queue to avoid the performance loss caused by repeated rebind operations.

What was done during the mission:

Review images

Set mPendingRebind to false as soon as the task executes so that subsequent requestRebind tasks can be sent to the main thread for rebind. For API 19 and above, check to see if the UI control is attached to the window. If not, set up a listener to rebind immediately when the UI is attached to the window and then return. ExecutePendingBindings execution logic is called when execution criteria are met (below API 19 or UI controls have been attached to a window).

Review images

However, the actual binding operation has not yet been performed. Here are some decisions to make before executing:

  1. If the bind has already started, that is, the code is executing, call requestRebind once and return.

  2. If there is no current need to refresh the UI, that is, the dirty flag is 0.

  3. The next in the implementation of specific executeBindings before operation, and call the mRebindCallbacks. NotifyCallbacks, notify all the callback that is about to start the rebind operation, the callback can be in the process of execution, will mRebindHalted set to true, ExecuteBindings are prevented from running, and successful interceptions are also notified by callback.

  4. If it is not intercepted, the executeBindings method is run, and when it is finished, it is also notified via a callback.

ExecuteBindings is an abstract method whose implementation is in a subclass, so we’re back in our ContentMainBinding class again. This means that the logic associated with content_main.xml is still embedded in the ContentMainBinding.

The executeBindings implementation is also generated at compile time by the data binding framework as follows:

View pictures View pictures

In the implementation, the dirty bit is first stored in a local variable, and then the dirty bit is set to 0 to start batch processing of previous changes. How do you know what needs to be done? According to the dirty flag bit and the associated value of the bitwise and operation to determine. At the end of the constructor, the dirty flag bit is set to 0x10L, or bit 5 is 1. In this case, each branch in the code above is true and executed, i.e., a full binding operation is performed.

Here it is done:

  1. If presenter is not null, mboundView4 will create the corresponding callback and set it to mboundView4.

  2. Update the values on the data model to the UI, such as firstName to mboundView1 and lastName to mboundView2. As you can see, each variable declared by the

    tag has a special tag bit. When the value of the change is updated, the corresponding dirty tag bit is set to 1. ExecuteBindings time changes back to update these changes to the corresponding control.

  3. On a control with bidirectional binding, add a listener to listen for changes, such as a TextWatcher on the EditText. Specific set placed in TextViewBindingAdapter logic. SetTextWatcher. The source code is as follows, which is to create a new TextWatcher enclosing the listener we passed in. Here you see the @BindingAdapter annotation, which maps control properties to method calls within the code that the data binding framework uses to generate method calls for the corresponding control at compile time. If you want custom controls to support data binding, see the implementation. View pictures View pictures

What are the listeners that we pass in to listen for code changes?

Review images

Is a InverseBindingListener, corresponding TextViewBindingAdapter setTextWatcher the fourth parameter, when the data changes, TextWatch at the end of the callback onTextChanged, The InverseBindingListener is used to retrieve the latest value from the View and check whether the *Binding class is null. If the Binding class is not null, call the corresponding method to update the data. In this way, business can customize the listener, and the function of data change monitor can be realized.

Review images

After updating the data, set the dirty bit to 1, in this case 0x8L, the fourth bit, and issue a rebind request.

Looking back at the executeBindings implementation above, you can see that the UI has been updated in the following branch:

Review images

The realization of the specific update UI on the TextViewBindingAdapter. SetText:

Review images

The implementation will compare whether the old data is consistent with the new data, and then update the data in the case of inconsistency, so as to avoid: Set data -> trigger data change callback -> update data -> trigger data change callback again ->… Cause the problem of endless circulation.

Method number problem

There are two JAR packages of data Binding framework, one is Adapter, the other is baseLibrary, the former method number is 415, the latter method number is 502, the total number of methods increased is less than one thousand. The number of generated class methods in the demo is about 20 per layout, depending on the number of variables in the layout (each variable corresponds to a GET and set method), and the number of bidirectional bindings (each variable corresponds to an additional anonymous InverseBindingListener class).

summary

Through the above wave of source code analysis, the operation mechanism of data binding in the application has been roughly analyzed again, and the following conclusions are summarized:

  1. By traversing the root view once and finding and binding all the controls in the view, the search efficiency is more efficient than using findViewById.

  2. The search process depends on the view’s tag. Avoid using tag tags as much as possible to avoid interfering with the normal operation of the framework

  3. All the UI operations are in the main thread; Operations on data can be performed in any thread;

  4. Data operations will not be immediately reflected on the UI, through the dirty flag, to the main thread to initiate a rebind task, the main thread in the next callback when the batch refresh, to avoid frequent UI operations;

  5. It is safer to manipulate the UI using data binding, with operations concentrated on the main thread and null-checking to avoid null Pointers before operations.

  6. Most of the logic is in the generated *Binding class, that is, the data Binding framework helps us do a lot of work at compile time, such as generating template code, implementing Binding logic, checking whether it is null, and generating proxy classes. The reliability of the code is also guaranteed by the compiler at compile time, which effectively reduces the possibility of human error.

Some ideas

  1. The separation of data and presentation is achieved with data binding, and the coding experience and efficiency can be further improved with the combination of responsive programming frameworks RxJava and RxAndroid.

  2. Since Data Binding separates Data from presentation, UI can be connected with Data Binding framework. Customized Adapter can be used to intervene in the reading and setting of some attributes, such as intercepting image resource loading (skin), dynamic character replacement (translation) and other functions.

  3. Convenient UI reuse. When UI is compontized on Android, it can be reused at the layout level. Business-independent UI logic can also be packaged together, while keeping the external interface (data model) simple, and the cost of learning access is smaller.


The resources

  1. Data Binding -Write App Faster(Google I/O 2015) www.youtube.com/watch?v=NBb…

  2. Advanced Data Binding(Google I/O 2016) v.youku.com/v_show/id_X…

  3. The Android Data Binding Library official introduction developer.android.com/topic/libra…

  4. Data Binding source android.googlesource.com/platform/fr…

  5. Data Binding(Google I/O 2015) Realm. IO /news/data-b…

  6. Data Binding guide Yanghui.name /blog/2016/0…

  7. MasteringAndroidDataBinding github.com/LyndonChin/…

  8. Data Binding senior blog. Zhaiyifan. Cn / 2016/07/06 /…