LiveData profile

  • Sticky data updates: How child threads update the UI (call setValue on the main thread and perform event distribution)

    • PostValue: The child thread updates the UI

    • Call the setValue

    • Enter main thread setValue:

      • [Fixed] Put sticky data into mData

      • And the version number of sticky data is ++(increased from -1 to 0).

  • How is sticky data triggered

    • Register in the Activity

    • Encapsulate host and observer

    • OnStateChanged is called in each of the host’s six life cycles, the last line of this function (line 376, next stage when the host is alive)

    • The host is alive (because activeStateChanged takes a bool and returns shouldBeActive, which returns true when the host is alive); At line 423, when the host is actually alive, the event is dispatched and the current this is passed in as the dispatchingValue parameter

    • DispatchingValue: @nullable ObserverWrapper initiator(ObserverWrapper is the parent of LifecycleBoundObserver)

      This is not empty, go in here

    • Compare data version numbers to determine whether to distribute sticky data; The original version number is -1, but before ++ (the sticky data version number of the host is 0, which is not equal to the data version -1 in the observer at this time, the following if exit is not entered; Will perform

      observer.mLastVersion = mVersion; //noinspection unchecked observer.mObserver.onChanged((T) mData); // Distribute sticky dataCopy the code
    • Compare version numbers to determine whether to distribute events: Distribute sticky data

    • Continue to call:

LiveDataBus

  • Summary:

    • Resolve stickiness events: Implementations can switch on or off stickiness (relying on Kotlin default parameters)

    • Solve a problem:

      • Use reflection to make a return when comparing version numbers; Event distribution is not performed

         if (observer.mLastVersion >= mVersion) {
             return;
         }
        Copy the code
  • Design idea:

    • Get host (mLasetVersion), observer (mVersion) by reflection, and then implement state alignment (mlastVersion.set (observerWraper, mVersionValue))

    • Code thread:

Concrete implementation steps

  • Design of the bus

    • code

       private val bus : MutableMap<String, BusMutableLiveData<Any>> by lazy { HashMap() }
      Copy the code
    • Parameter Description: Why use Map

      • // Encapsulate bus class: store the subscriber. For program extensibility (it is convenient for users to specify operation objects, users want to operate on a certain MyLiveData), so it is made into Map type: Map type: key: the name of this data. Any: the type of this dataCopy the code
      • Key: Identifies the name of the data, as shown in the red box below

      • Value: BusMutableLiveData< Any > :

        • The user uses parameters to specify which type of data to operate on
        • Unlike the previous which directly write death, so the scalability is better
      • Use lazy loading: A good feature of Kotlin

      • HashMap: Data structure

  • Design with functions: Expose functions to register subscriber relationships with the outside world

    • Summary:

      BusMutableLiveData () {BusMutableLiveData () {BusMutableLiveData () {BusMutableLiveData () {BusMutableLiveData () {BusMutableLiveData (); // The second parameter: control the change, directly pass the corresponding bytecode file // the third parameter: switch, whether to enable data stickiness (kotlin default parameter, the default is on data stickiness) */Copy the code
    • Code:

      @Synchronized fun <T> with(key: String, type: Class<T>, isStick: Boolean = true) : BusMutableLiveData<T> { if (! Bus.containskey (key) {// Check whether there is a duplicate key bus[key] = BusMutableLiveData(isStick)// Operator overload} return bus[key] as BusMutableLiveData<T>// cast}Copy the code
  • Design the BusMutableLiveData class:

  • Designing hook functions: Use reflection

    • Target: Get mLastVersion(host), get mVersion(observer), align (assign directly to it) and no sticky distribution will occur

    • Code: Kotlin

      private fun hook(observer: Observer<in T>) { // TODO 1. Get mLastVersion // Get the mObservers object in LivData's class, ::class is Kotlin's, Val mObserversField = LiveData::class. Field = liveDataClass. GetDeclaredField (" mObservers ") mObserversField. IsAccessible = true / / private modifiers can also access / / access to the member variable object Any == Object val mObserversObject: Any = mobserversField. get(this) private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = val mObserversClass: Class < * > = mObserversObject. JavaClass / / access to mObservers object the get method protected Entry < K, V > get (K K) {val get: Method = mObserversClass.getDeclaredMethod("get", Any::class.java) get. IsAccessible = true // Private modifiers can also access // the get method val invokeEntry: Any = get.invoke(mObserversObject, observer) // Invoke (mObserversObject, observer) // Invoke (mObserversObject, observer) Any? = null if (invokeEntry ! = null && invokeEntry is Map.Entry<*, *>) { observerWraper = invokeEntry.value } if (observerWraper == null) { throw NullPointerException("observerWraper is Null ")} / / get observerWraperr Class object val supperClass: Class. < * > = observerWraper javaClass. The superclass val mLastVersion: Field = supperClass.getDeclaredField("mLastVersion") mLastVersion.isAccessible = true // TODO 2. Val mVersion: Field = liveDataClass. GetDeclaredField (" mVersion ") mVersion. IsAccessible = true / / TODO 3. MLastVersion = mVersion, Val mVersionValue: Any = mversion.get (this) mlastversion.set (observerWraper, mVersionValue)}Copy the code
    • Code: Java Version:

      private void hook(Observer<? super T> observer) { try { // TODO 1. Class<LiveData> liveDataClass = livedata.class; Field mObserversField = liveDataClass.getDeclaredField("mObservers"); mObserversField.setAccessible(true); Object mObserversObject = mobServersField.get (this); // Get the map object class <? > mObserversClass = mObserversObject.getClass(); / / get access to mObservers Object Method get = mObserversClass. GetDeclaredMethod (" get ", the Object. The class); get.setAccessible(true); InvokeEntry = get.invoke(mObserversObject, observer); ObserverWraper = null; if (invokeEntry ! = null && invokeEntry instanceof Map.Entry) { observerWraper = ((Map.Entry) invokeEntry).getValue(); } if (observerWraper == null) { throw new NullPointerException("observerWraper is null"); } // get the observerWraperr Class<? > supperClass = observerWraper.getClass().getSuperclass(); Field mLastVersion = supperClass.getDeclaredField("mLastVersion"); mLastVersion.setAccessible(true); / / TODO 2. Get mVersion Field mVersion = liveDataClass. GetDeclaredField (" mVersion "); mVersion.setAccessible(true); // TODO 3.mLastVersion=mVersion Object mVersionValue = mVersion.get(this); mLastVersion.set(observerWraper, mVersionValue); } catch (Exception e) { e.printStackTrace(); }}Copy the code
  • Complete code with sticky switch:

    package com.derry.livedatabusandviewbinding.simple1 import android.util.Log import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import Java.lang.reflect. Field import java.lang.reflect.Method /** * Singleton mode sets a switch, */ object OKLiveDataBusKT {/* // Encapsulate bus class: Map type: key: specifies the name of this data. Any: specifies the type of this data. // Try to use lazy loading */ private val bus: BusMutableLiveData< String, BusMutableLiveData<Any>> by lazy {HashMap()} With generics, once the generic T type of the argument to the with function is determined, the generic T type of its return value is determined randomly and so are the two generic T types in BusMutableLiveData below. // Synchronized fun <T> with(key: String, type: Synchronized fun <T> with(key: String, type: Synchronized fun <T> Class<T>, isStick: Boolean = true) : BusMutableLiveData<T> { Bus.containskey (key) {bus[key] = BusMutableLiveData(isStick)// Operator overload} return bus[key] as BusMutableLiveData<T>// Cast Class BusMutableLiveData<T> private constructor() class BusMutableLiveData<T> constructor() MutableLiveData<T>() {// Sticky switch var isStick: Boolean = false constructor: constructor(isStick: Boolean) : Override fun observe(owner: override fun observe(owner: override fun observe(owner: override fun observe(owner: override fun observe(owner: override fun observe(owner: override fun observe(owner: override fun observe(owner: override fun observe(owner: override fun observe(owner: LifecycleOwner, observer: observer <in T>) {/* this sentence can not be deleted, hook operation can not destroy the original system, just open a way; And when the following code is executed, it is only a registration; */ super.observe(owner, observer) // This sentence will execute the parent class // enable the system function // use the argument to determine whether to enable sticky if (! IsStick) {hook(observer = observer) log.d ("derry", "Kotlin version is not enabled for viscosity ")} else {log.d ("derry", Private fun hook(observer: observer <in T>) {// TODO 1 Get mLastVersion // Get the mObservers object in LivData's class, Val liveDataClass = LiveData::class. Java // get val mObserversField: Field = liveDataClass. GetDeclaredField (" mObservers ") mObserversField. IsAccessible = true / / private modifiers can also access / / access to the member variable object Any == Object val mObserversObject: Any = mobserversField. get(this) private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = val mObserversClass: Class < * > = mObserversObject. JavaClass / / access to mObservers object the get method protected Entry < K, V > get (K K) {val get: Method = mObserversClass.getDeclaredMethod("get", Any::class.java) get. IsAccessible = true // Private modifiers can also access // the get method val invokeEntry: Any = get.invoke(mObserversObject, observer) // Invoke entry value is "AAA" is String is is Any? = null if (invokeEntry ! = null && invokeEntry is Map.Entry<*, *>) { observerWraper = invokeEntry.value } if (observerWraper == null) { throw NullPointerException("observerWraper is Null ")} / / get observerWraperr Class object val supperClass: Class. < * > = observerWraper javaClass. The superclass val mLastVersion: Field = supperClass.getDeclaredField("mLastVersion") mLastVersion.isAccessible = true // TODO 2. Val mVersion: Field = liveDataClass. GetDeclaredField (" mVersion ") mVersion. IsAccessible = true / / private can also access / / TODO 3.mLastVersion=mVersion val mVersionValue: Any = mversion.get (this) mlastversion.set (observerWraper, mVersionValue)// State alignment}}}Copy the code
  • Use: To call the with function, the stickiness switch is the third argument, which is turned on by default

  • Usage Scenarios:

    • When doing the login function, only want to deal with the data of this time, do not need to deal with the previous stickiness data (stickiness data will bring bugs), then need to remove stickiness
  • Implementation details:

    • Sticky data: trigger after subscription, can still receive the last message (before trigger), this data is sticky data

ViewBinding

  • Usage Scenarios:

    • Implement simple functions, belong to lightweight DataBinding;
    • Gradle plugin scans the layout directly, not APT annotation processor (build annotation scan at compile time)
  • Advantages and disadvantages of DataBinding:

    • Because two-way binding in DataBinding is a performance comparison (in MVVM, MVP, DataBinding as the holder of performance loss)
    • But DataBinding is powerful enough to use APT annotation processor technology to scan the entire layout and just do it
  • ViewBinding use:

    • Open it, and the ViewBinding for the entire project is open

    • I don’t have to say findViewById anymore

    • At the same time, a very strange phenomenon occurs:

      • An ActivityMain2Binding will be created for MainActivity2
      • Clicking on it takes you directly to activity_main2.xml

MainActivity2

package com.derry.view_binding; import android.os.Bundle; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.derry.view_binding.databinding.ActivityMain2Binding; import com.derry.view_binding.databinding.TestBinding; // What does the APT annotation handler use behind the ViewBinding? Public class MainActivity2 extends AppCompatActivity {ActivityMain2Binding main2Binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); main2Binding = ActivityMain2Binding.inflate(getLayoutInflater()); setContentView(main2Binding.getRoot()); /* main2Binding.tv1.setText("AAA"); main2Binding.tv2.setText("BNB"); main2Binding.tv3.setText("DDDD"); * /}}Copy the code

activity_main2.xml

<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="30sp" android:textColor="@color/teal_200" /> <TextView android:id="@+id/tv2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="30sp" android:textColor="@color/purple_500" /> <TextView android:id="@+id/tv3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="30sp" android:textColor="@color/black" /> </LinearLayout>Copy the code
  • Effect:

    • Comment out the setText statement in MainActivity2

    • Uncommented setText statement in MainActivity2:

The Kotlin-Android-Extensions plug-in works with ViewBinding technology

  • ViewBinding:

    • Always layout oriented (always linked to layout), Gradle plug-ins scan layout files to generate corresponding classes after the project is compiled;
    • Object-oriented design idea, the layout file as an object; Generate corresponding classes after the project is compiled;
    • Later than the Kotlin-Android-Extensions
  • Kotlin-android-extensions

    • How it works: Kotlin’s binding mechanism

    • This is also a Gradle plugin

    • Appeared before ViewBinding and became popular

    • There are risks to use: human error causing access to a control in another XML file in the current Activity: null pointer exception

      • Use this plugin to access test.xml controls in MainActivity2.
      • ViewBinding doesn’t have this problem: it doesn’t even prompt
  • Code display:

    • Environment preparation: Edit build.gradle

      • Import plug-in:

        Plugins {id' com.android. Application 'id' kotlin-android' Findv Gradle plugin id 'kotlin-android-extensions'Copy the code
      • Enable ViewBinding

        ViewBinding ViewBinding {enabled true}Copy the code
    • Engineering structure Display:

      • Activity_main. XML: Just three TextView controls

      • Text.xml: This has a Button in it

    • Code test: Verify plugin flaws (relying on plugin to access text.xml controls in MainActivity)

      • MainActivity: As you can see here, there is no error

      • Real machine test results:

    • Code test: Verify the ViewBinding benefits

      • Screenshot: You can see that the three TextView controls in Activity_main.xml are accessible

      • Controls (Button in test.xml) outside the current layout file (activity_main.xml) cannot be accessed using a ViewBinding

ViewBinding principle

  • Summary: Use Gradle plugin to scan layout files and dynamically generate classes that encapsulate findViewById.

  • You need to be familiar with Gradle’s principles to understand how they work

  • Source code validation:

    • Turn on the ViewBinding switch

    • Edit a layout file: test.xml

    • Open the path to the

    • Compile the project:

    • In this path, the layout file corresponds to the generated object, which contains the TestBinding class we need

    • There are controls for the layout file in the generated TestBinding class

    • The bottom layer is still findViewById, but it’s wrapped in a friendlier way