The introduction

This article is a study notes after learning the big man’s article and writing after hands-on practice. Big guy’s article is written better, I write myself may not describe so clearly 😂, so many places in this article are directly quoted big guy’s article.

Project source: github.com/LinYaoTian/…

LIfeCycle and LiveData for Jetpack are best understood by reading this article.

Reference:

  • Evolution of Android message Bus: Replace RxBus and EventBus with LiveDataBus
  • Implement event bus idea and scheme based on LiveData
  • Lifecycle will begin with Android Jetpack. Lifecycle will begin with Lifecycle.
  • Android Jetpack Architecture Components (part 4)
  • Android Jetpack Architecture Components (part 5)

The basic concept

For Android system, message passing is the most basic component, each App in different pages, different components are carrying out message passing.

Messaging can be used for communication before Android’s four major components, as well as before asynchronous threads and the main thread.

For Android developers, there are many kinds of messaging methods commonly used, from the earliest Handler, BroadcastReceiver, interface callback, to the popular communication bus frameworks EventBus and RxBus in recent years.

EventBus

When it comes to Android’s communication bus class framework, we have to mention EventBus.

EventBus is an Android event publish/subscribe framework that simplifies Android event delivery by decoupling publishers and subscribers. EventBus can replace traditional Android intents, handlers, Broadcast, or interface callbacks to transmit data and execute methods between fragments, activities, and Service threads.

The best thing about EventBus is its simplicity and decoupling.

Before EventBus, we often used broadcasts to implement listening, or custom interface function callbacks. In some cases, we could carry simple data directly with intEnts, or handle message passing through handlers between threads. But both broadcast and Handler mechanisms are far from adequate for efficient development. EventBus simplifies communication between components within an application and between components and background threads. Since its launch, EventBus has been embraced by developers.

Now it looks like EventBus has brought a new framework and idea to the Android developer world: publishing and subscribing messages. This idea has been applied in many subsequent frameworks.

Subscribe/publish model

The subscription publishing pattern defines a one-to-many dependency that allows multiple subscriber objects to listen to a topic object at the same time. The topic object notifies all subscriber objects when its state changes, enabling them to update their state automatically.

The subscription/publish model is very similar to the Observer model, and I think the subscription/publish model is an enhanced version of the Observer model.

The differences between the two are:

  • In the observer mode, there is a direct dependence between the observer and the observed.
  • In the subscription/publication mode, there is a scheduling center before the subscriber and the publisher, which avoids the dependency between the subscriber and the publisher.

Observer vs. publish-subscribe

LiveData

This article implements EventBus through LiveData, which is introduced here.

LiveData is, as its name suggests, an observable data holder. Unlike a normal Observable, LiveData is life cycle aware, which means it can properly handle life cycles in activities, fragments, and services.

In fact LiveData doesn’t work alone, it depends on the LifeCycle component of the Jectpack because LifeCycle is wrapped up in the parent class of the Activity and is essentially unsentient when we use LiveData. So LifeCycle will be mentioned briefly here.

The role of the LifeCycle

  • Manage the life cycle of components.
  • Enable third-party businesses to access the lifecycle of dependent components internally, facilitating timely suspension and avoiding missed execution opportunities.

Those of you who want to know more about it can read these two articles:

  • Lifecycle will begin with Android Jetpack. Lifecycle will begin with Lifecycle.
  • Lifecycle will help Lifecycle Lifecycle Lifecycle

Moving on to LiveData:

The data source for LiveData is typically a ViewModel, or it can be any other LiveData component that can be updated.

Ordinarily we use LiveData’s Observe () to notify all its active observers when data is updated. Unlike RxJava, LiveData will only notify active observers. For example, the Activity is inactive when it is in the Destroyed state and therefore will not be notified.

We can also subscribe using LiveData’s observerForever() method. The difference is that observerForever() is not affected by the life cycle of components such as activities and is notified whenever data is updated.

Simple use of LiveData:

public class MainActivity extends AppCompatActivity {
   private static final String TAG="MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
      	// Create a LiveData object
        MutableLiveData<String> mutableLiveData  = new MutableLiveData<>();
        // Start subscribing
      	mutableLiveData.observe(this.new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String s) {
                Log.d(TAG, "onChanged:"+s); }});// Update the data, eventually calling back the onChange() method above
        mutableLiveData.postValue("Android Advanced Trilogy"); }}Copy the code

More details on LiveData can be found in this article, which introduces you to LiveData.

What are the advantages of implementing through LiveData?

EventBus is a well-known communication class bus library in the industry, but it also has many shortcomings that are criticized:

  1. Manual registration and unregistration are required, which may cause memory leaks if you are not careful.
  2. It is difficult to trace the event source that went wrong when using EventBus.
  3. Each event defines an event class, which is prone to class bloat.

LiveDataBus, implemented through LiveData, has the following advantages:

  1. With life cycle awareness, no manual registration and anti-registration.
  2. Has a unique trusted event source.
  3. Separate each event with a string to avoid class bloat.
  4. LiveData is the official Android library, which is more reliable.

The principle of

The composition of LiveDataBus

  • Messages: Messages can be any Object and can define different types of messages, such as Boolean and String. You can also define messages of a custom type.
  • Message channel: LiveData plays the role of a message channel. Different message channels are distinguished by different names. The name is a String and you can obtain a LiveData message channel by name.
  • Message Bus: The message bus is implemented as a singleton, with different message channels stored in a HashMap.
  • Subscribe: The subscriber gets the message channel through getChannel(), and then calls Observe () to subscribe to the message for this channel.
  • Publish: The publisher retrieves the message channel via getChannel() and publishes the message by calling setValue() or postValue().

The principle diagram of the LiveDataBus

Simple implementation

With LiveData we can implement an event publish/subscribe framework very simply:

public final class LiveDataBus {

    private final Map<String, MutableLiveData<Object>> bus;

    private LiveDataBus(a) {
        bus = new HashMap<>();
    }

    private static class SingletonHolder {
        private static final LiveDataBus DATA_BUS = new LiveDataBus();
    }

    public static LiveDataBus get(a) {
        return SingletonHolder.DATA_BUS;
    }

    public <T> MutableLiveData<T> getChannel(String target, Class<T> type) {
        if(! bus.containsKey(target)) { bus.put(target,new MutableLiveData<>());
        }
        return (MutableLiveData<T>) bus.get(target);
    }

    public MutableLiveData<Object> getChannel(String target) {
        returngetChannel(target, Object.class); }}Copy the code

That’s right, in just 27 lines we’ve implemented an event publish/subscribe framework!

Problem a

LiveData was great for a while, but once we were done with it, we found that the problem with this simple LiveDataBus was that subscribers would receive messages published before subscribing, similar to sticky messages. For a message bus, both sticky and non-sticky messages must be supported. Let’s look at how to solve this problem.

Let’s take a look at why this is happening:

Subscription method for LiveData

android.arch.lifecycle.LiveData

@MainThread
   public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
       assertMainThread("observe");
       // The current subscription request will be ignored when the currently bound component is in a DESTROYED state
       if (owner.getLifecycle().getCurrentState() == DESTROYED) {
           return;
       }
       // Convert to an observer wrapper class with lifecycle awareness
       LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
       ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
       // The corresponding observer can be bound to only one owner
       if(existing ! =null && !existing.isAttachedTo(owner)) {
           throw new IllegalArgumentException("Cannot add the same observer"
                   + " with different lifecycles");
       }
       if(existing ! =null) {
           return;
       }
       Registration / / lifecycle
       owner.getLifecycle().addObserver(wrapper);
   }
Copy the code

As you can see, LiveData internally wraps our incoming parameters as wrappers, stores them in a Map, and finally adds observers via the LifeCycle component.

Update data methods for LiveData

LiveData has two ways to update data, one is setValue() and the other is postValue(). The difference between these two methods is that postValue() is internally thrown into the main thread to perform the update, so it is suitable for use in child threads. SetValue () updates the data directly.

Let’s just look at the setValue() method

android.arch.lifecycle.LiveData

@MainThread
protected void setValue(T value) {
    assertMainThread("setValue");
    // Send version +1
    mVersion++;
    mData = value;
    // Information distribution
    dispatchingValue(null);
}
Copy the code

Remember mVersion here, which is critical because it increments with each update. The default value is -1. Then we follow up with the dispatchingValue() method:

android.arch.lifecycle.LiveData

void dispatchingValue(@Nullable ObserverWrapper initiator) {
        // mDispatchingValue is used to handle concurrent calls to dispatchingValue
        // If new data changes occur during execution, the observer will not be notified again
        // So the execution within the observer should not do time-consuming work
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if(initiator ! =null) {
              	/ / here
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                  	/ / here
                    considerNotify(iterator.next().getValue());
                    if (mDispatchInvalidated) {
                        break; }}}}while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

Copy the code

As you can see, let’s consider the notify () method eventually, so let’s move on:

android.arch.lifecycle.LiveData

private void considerNotify(ObserverWrapper observer) {
        if(! observer.mActive) {return;
        }
        if(! observer.shouldBeActive()) { observer.activeStateChanged(false);
            return;
        }
  			/ / determine the version
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        //noinspection unchecked
        observer.mObserver.onChanged((T) mData);
    }

Copy the code

Finally it’s the most important time!! If the mLastVersion of ObserverWrapper is less than the mVersion of LiveData, the onChange() method is executed to notify the observer that the data has been updated.

And ObserverWrapper. MLastVersion default value is 1, LiveData as long as the updated data, mVersion will certainly be greater than 1, so the subscriber will soon receive subscription before the latest news!!

Question 2

Let’s look at the comment on LiveData’s postValue() method:

As you can see, the comment states that if the postValue() method is called multiple times at the same time in multiple threads, only the value of the last call will be updated. It is possible for this method to lose events!!

Take a look at the source:

android.arch.lifecycle.LiveData

    private final Runnable mPostValueRunnable = new Runnable() {
        @Override
        public void run(a) {
            Object newValue;
            synchronized (mDataLock) {
                newValue = mPendingData;
              	// Set mPendingData to not_setmPendingData = NOT_SET; } setValue((T) newValue); }};protected void postValue(T value) {
        boolean postTask;
        synchronized (mDataLock) {
          	// Check whether postTask is not_set
            postTask = mPendingData == NOT_SET;
            mPendingData = value;
        }
        if(! postTask) {return;
        }
        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
    }
Copy the code

PostValue simply stores the data passed in to mPendingData, then throws a Runnable to the main thread, and calls setValue inside the Runnable to actually set the stored value and call back the observers. If the Runnable is postValue multiple times before it is executed, it simply changes the temporary value of mPendingData and does not throw another Runnable.

Finally realize

Knowing the root cause of the problem, we can solve it.

There are many solutions to solve this problem, including the evolution of the Android message bus by using LiveDataBus instead of RxBus and EventBus to modify the mVersion value in LiveData. Another solution is to implement the event bus idea and solution based on LiveData. This solution is based on the custom Observer wrapper class. Since sticky messages end up calling the Observer#onChange() method, we customize the Observer wrapper class and maintain the actual number of subscribed messages ourselves. To determine whether the real onChange() method needs to be triggered. This article uses the second scheme.

Step 1: Customize the Observer wrapper class

For business extensibility, here we define a Base class:

internal open class BaseBusObserverWrapper<T>(private val mObserver: Observer<in T>, private val mBusLiveData: BusLiveData<T>) : Observer<T> {

    private val mLastVersion = mBusLiveData.version

    private val TAG = "BaseBusObserverWrapper"

    override fun onChanged(t: T?). {
        Logger.d(TAG,"msg receiver = " + t.toString())
        if (mLastVersion >= mBusLiveData.version){
            // The version number of LiveData has not been updated, indicating that there is no new data, just because
            // Call onChange() due to a lower version of the current Observer than LiveData
            return
        }
        try {
            mObserver.onChanged(t)
        }catch (e:Exception){
            Logger.e(TAG,"error on Observer onChanged() = " + e.message)
        }
    }

    open fun isAttachedTo(owner: LifecycleOwner) = false

}
Copy the code

Here we save the mVersion value of LiveData, and each time onChange() is executed, we first determine whether some LiveData has been updated. If not, the observer.onchange () method of the Observer is not executed.

Then define two subclasses, BusLifecycleObserver and BusAlwaysActiveObserver, where BusLifecycleObserver is used for events subscribed to by the LiveData#observer() method, BusAlwaysActiveObserver is used for events subscribed to by the LiveData#observerForever() method.

internal class BusLifecycleObserver<T>(private val observer: Observer<in T>, private val owner: LifecycleOwner, private val liveData: BusLiveData<T>)
    : BaseBusObserverWrapper<T>(observer,liveData),LifecycleObserver{

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy(a){
        liveData.removeObserver(observer)
        owner.lifecycle.removeObserver(this)}}Copy the code

For BusLifecycleObserver, the observer needs to be removed while the lifecycle component is in Destroyed.

internal class BusAlwaysActiveObserver<T>(private val mObserver: Observer<in T>, private val mBusLiveData: BusLiveData<T>)
    : BaseBusObserverWrapper<T>(mObserver, mBusLiveData)
Copy the code

For BusAlwaysActiveObserver, the observer is not affected by the component lifecycle and therefore does not need to be removed when the component is Destroyed.

Step 2: Customize the LiveData class

Note: Because LiveData’s getVersion() is package-access! So BusLiveData must be defined in the same package as LiveData, androidx.lifecycle, so you need to create your own package with the same name and put BusLiveData in it.

If your project doesn’t include Android X, you can also use android.arch.lifecycle for v7. Lifecycle does not change the generic type of LiveData method parameters in android.arch.lifecycle, so copying this code directly will be a bit of a problem and you will need to modify it yourself as prompted by the compiler.

class BusLiveData<T>(private val mKey:String) : MutableLiveData<T>() {

    private val TAG = "BusLiveData"

    private val mObserverMap: MutableMap<Observer<in T>, BaseBusObserverWrapper<T>> = mutableMapOf()

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        val exist = mObserverMap.getOrPut(observer,{
            BusLifecycleObserver(observer,owner,this).apply {
                mObserverMap[observer] = this
                owner.lifecycle.addObserver(this)}})super.observe(owner, exist)
        Logger.d(TAG,"observe() called with: owner = [$owner], observer = [$observer]. "")}@MainThread
    override fun observeForever(observer: Observer<in T>) {
        super.observeForever(observer)
        val exist = mObserverMap.getOrPut(observer ,{
            BusAlwaysActiveObserver(observer,this).apply {
                mObserverMap[observer] = this}})super.observeForever(exist)
        Logger.d(TAG, "observeForever() called with: observer = [$observer]. "")}@MainThread
    fun observeSticky(owner: LifecycleOwner, observer: Observer<T>) {
        super.observe(owner, observer)
        Logger.d(TAG, "observeSticky() called with: owner = [$owner], observer = [$observer]. "")}@MainThread
    fun observeStickyForever(observer: Observer<T>){
        super.observeForever(observer)
        Logger.d(TAG, "observeStickyForever() called with: observer = [$observer]. "")}@MainThread
    override fun removeObserver(observer: Observer<in T>) {
        valexist = mObserverMap.remove(observer) ? : observersuper.removeObserver(exist)
        Logger.d(TAG, "removeObserver() called with: observer = [$observer]. "")}@MainThread
    override fun removeObservers(owner: LifecycleOwner) {
        mObserverMap.iterator().forEach {
            if (it.value.isAttachedTo(owner)) {
                mObserverMap.remove(it.key)
            }
        }
        super.removeObservers(owner)
        Logger.d(TAG, "removeObservers() called with: owner = [$owner]. "")}override fun postValue(value: T) {
       mMainHandler.post {
           setValue(value)
       }
    }

    @MainThread
    override fun onInactive(a) {
        super.onInactive()
        if(! hasObservers()) {// When LiveData has no active observers, the associated instances can be removed
            LiveDataBusCore.getInstance().mBusMap.remove(mKey)
        }
        Logger.d(TAG, "onInactive() called")}@MainThread
    public override fun getVersion(a): Int {
        return super.getVersion()
    }


}
Copy the code

The code is short and simple, but the point is that for non-sticky messages, namely Observer () and observerForever(), we need to wrap the processing with custom wrapper classes. For sticky messages, just use the LiveData default implementation.

Also rewrite the postValue() method to internally execute setValue() via mainHandler.post () to avoid postValue() losing data.

Step 3: Define management classes

internal class LiveDataBusCore {

    companion object{

        @JvmStatic
        private val defaultBus = LiveDataBusCore()

        @JvmStatic
        fun getInstance(a) = defaultBus
    }

    internal val mBusMap : MutableMap<String, BusLiveData<*>> by lazy {
        mutableMapOf<String, BusLiveData<*>>()
    }

    fun <T> getChannel(key: String) : BusLiveData<T> {
        return mBusMap.getOrPut(key){
            BusLiveData<T>(key)
        } as BusLiveData<T>
    }
}
Copy the code

Step 4: Define the entry class

class LiveDataBus {

    companion object{

        @JvmStatic
        @Synchronized
        fun <T> getSyn(key: String) : BusLiveData<T>{
            return get(key)
        }
      
        @JvmStatic
        fun <T> get(key: String) : BusLiveData<T>{
            return LiveDataBusCore.getInstance().getChannel(key)
        }
      
        private fun <T> get(key: String, type: Class<T>) : BusLiveData<T> {
            return LiveDataBusCore.getInstance().getChannel(key)
        }

        @JvmStatic
        fun <E> of(clz: Class<E>): E {
            require(clz.isInterface) { "API declarations must be interfaces." }
            require(clz.interfaces.isEmpty()) { "API interfaces must not extend other interfaces." }
            return Proxy.newProxyInstance(clz.classLoader, arrayOf(clz), InvocationHandler { _, method, _->
                return@InvocationHandler get(
                        // The event name is defined by the collection class name _ event method name
                        // To ensure that the event is unique
                        "${clz.canonicalName}_${method.name}",
                        (method.genericReturnType as ParameterizedType).actualTypeArguments[0].javaClass)
            }) as E
        }
    }


}
Copy the code

Use:

class TestLiveDataBusActivity : AppCompatActivity() {
  
    companion object{
        private const val TAG = "TestLiveDataBusActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_live_data_bus)

        LiveDataBus.get<String>("testObserver").observe(this, Observer<String> {
            Log.d(TAG, "testObserver = $it")
            test_observer_id.text = it
        })
      	
      	LiveDataBus.get<String>("testObserver").postValue("new value")}}Copy the code

LiveDataBus also provides an of() method for providing event constraints. What are time constraints? That is, the key of our current observer and publisher access channel is a string, and it may be used in the process that the publisher’s key is itO and the observer’s key is entered as IT0 by mistake, so here we can emulate the Retrofit request dynamic proxy. To do this, we need to define an interface:

interface TestLiveEvents {
    fun event1(a): MutableLiveData<String>
}
Copy the code

Use:

fun main(a) {
    LiveDataBus
            .of(TestLiveEvents::class.java)
            .event1()
            .postValue("new value")}Copy the code

conclusion

With LiveData provided by Android, we can easily implement our own LiveDataBus with only the above classes, while avoiding many of the drawbacks of EventBus!

LiveDataBus source: github.com/LinYaoTian/…

If you have any questions about this article, please point them out in the comments section.