background

EventBus, RxBus, is the most popular Android messaging framework in recent years. Generally speaking, it is easy to use, simple, decoupled, we can easily pass messages, so why do we now want to re-create a message bus framework wheel?

Which brings us to LiveData, which by nature is observer mode, has a lot of overlap with EventBus and, as a member of Jetpack, works well with Lifecycle and ViewModel, Nowadays, there are a lot of people on the Internet who want to replace EventBus with LiveData, which has the following advantages:

  • Perception lifecycle
  • There is no need to call the de-registration method

Here’s a sneak peek at the LiveData structure:

The message sent by EventBus is global. In some cases, the message sent by EventBus is only expected to be received inside the page, such as two fragments of the same Activity. In this case, it is troublesome to use Event. But it’s too cumbersome to define interfaces and callbacks, so it’s LiveData’s turn (plus ViewModel), which is what Google recommends. Let’s focus on the implementation of multiplexing LiveData

The specific implementation

Using LiveData to implement the message bus is very simple, a file can be solved, many examples on the Internet, here is a very simple implementation:

public class LiveEventBus {

    public static LiveEventBus get() {
       return LiveEventBusHolder.instance;
    }

    private static class LiveEventBusHolder {
        static LiveEventBus instance = new LiveEventBus();
    }

    private Map<String, MutableLiveData<? extends LiveBusEvent>> mBus = new ArrayMap<>();
   
    @NonNull
    public <T extends LiveBusEvent> MutableLiveData<T> of(@NonNull Class<T> clazz) {
        String eventName = clazz.getName();
        MutableLiveData liveData = mBus.get(eventName);
        if (liveData == null) {
            liveData = new MutableLiveData();
            mBus.put(eventName, liveData);
        }

        return liveData;
    }

    public <T extends LiveBusEvent> void post(@NonNull T event) {
        MutableLiveData liveData = of(event.getClass());
        liveData.setValue(event);

    }


    interface LiveBusEvent {}}Copy the code

Listen to the message

LiveEventBus.get()
    .of(MyEvent.class)
    .observe(this, new Observer<MyEvent>() {
            @Override
            public void onChanged(MyEvent myEvent) {

            }
        });
Copy the code

Send a message

LiveEventBus.get().post(new MyEvent("hello world"));
Copy the code

This completes an event bus.

LiveData’s here, however, the message bus, there is a very serious problem, that is, it only supports the viscous event that is to say, if we send a message first, and then through observe listening news, you will immediately receive a message, this is we don’t want to see, so the next major need to solve the problem of viscous events.

Analysis and analysis of viscous events

The Android Message Bus evolution: Replacing RxBus and EventBus with LiveDataBus

For a key reason, there is a member variable version of type int in LiveData

public abstract class LiveData<T> {

    static final int START_VERSION = -1;

    private int mVersion = START_VERSION;

    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null); }}Copy the code

This version is used to record the number of times LiveData will be set, starting with -1 and incrementing by 1 each time, and if Lifecycle will be sent immediately after processing the active state in the Observer, this method will be called immediately:

private void considerNotify(ObserverWrapper observer) {

    if(! observer.mActive) {return;
    }

    if(! observer.shouldBeActive()) { observer.activeStateChanged(false);
        return;
    }

    if (observer.mLastVersion >= mVersion) {
        // Mainly here
        return;
    }

    observer.mLastVersion = mVersion;

    //noinspection unchecked
    observer.mObserver.onChanged((T) mData);

}
Copy the code

Observer is an ObseverWraper class that wraps the observer. The value of mLastVersion will always start with -1, so when a new class is registered, if the value has been set before, the mVersion of LiveData will not be -1. Will be observer.mlastVersion, and our registered observer will receive the event.

Therefore, in order to solve the problem of sticky events, it is natural to change the value of version. However, both mVersion and mLastVersion are private and cannot be obtained directly, so the method of reflection retrieval is generated. This article of Meituan solves this problem.

Reflection is always unsafe, so here’s my solution: since we can’t modify LiveData’s internal members, why not implement it ourselves and record our own version value?

public class BusLiveData<T> extends MutableLiveData<T> {

    private static final int VERSION_START = -1;
    private int activeVersion = VERSION_START;
    private ArrayMap<Observer<? super T>, BusObserver<? super T>> busObservers = new ArrayMap();

    @Override
    public void setValue(T value) {
        activeVersion++;
        super.setValue(value);
    }



    private BusObserver<? super T> createBusObserver(@NonNull Observer<? super T> observer, int latestVersion) {
        BusObserver<? super T> busObserver = busObservers.get(observer);
        if (busObserver == null) {
            busObserver = new BusObserver(observer, latestVersion);
            busObservers.put(observer, busObserver);
        } else {
            throw new IllegalArgumentException("Please not register same observer " + observer);
        }
        return busObserver;

    }

    @Override

    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            return;
        }
        super.observe(owner, createBusObserver(observer, activeVersion));
    }

    public void observeSticky(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
           return;
        }
        super.observe(owner, createBusObserver(observer, VERSION_START));
    }



    @Override
    public void observeForever(@NonNull Observer<? super T> observer) {
        super.observeForever(createBusObserver(observer, activeVersion));
    }

    public void observeStickyForever(@NonNull Observer<T> observer) {
        super.observeForever(createBusObserver(observer, VERSION_START));
    }

    @Override
    public void removeObserver(@NonNull Observer<? super T> observer) {
        BusObserver<? super T> busObserver;
        if (observer instanceof BusObserver) {
            busObserver = (BusObserver) observer;
        } else {
            busObserver = busObservers.get(observer);
        }

        if(busObserver ! =null) {
            busObservers.remove(busObserver.realObserver);
            super.removeObserver(busObserver); }}private class BusObserver<M extends T> implements Observer<M> {

        private Observer<M> realObserver;
        private int lastVersion;

        public BusObserver(@NonNull Observer<M> realObserver, int lastVersion) {
            this.realObserver = realObserver;
            this.lastVersion = lastVersion;
        }

        @Override
        public void onChanged(M m) {
            if (activeVersion <= lastVersion) {
                return;
            }
            lastVersion = activeVersion;
            if(m ! =null) { realObserver.onChanged(m); }}}}Copy the code

Since messaging needs to be non-sticky in most cases, I changed Observe’s default method to non-sticky and provided observeSticky to listen for sticky events.

In-page communication

Speaking of LiveData, we have to mention ViewModel, so to communicate on the page, just need to implement a general ViewModel observeSticky can be used separately

public class PageEventBus extends ViewModel {

    private static PageEventBus obtain(@NonNull ViewModelStoreOwner owner) {
        return new ViewModelProvider(owner, FACTORY).get(PageEventBus.class);
    }

    // omit others
    public static ViewModelProvider.Factory FACTORY = new ViewModelProvider.Factory() {

        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            return(T) new PageEventBus(); }}; }Copy the code

Send messages by customizing events

PageEventBus uses class to distinguish the types of messages to be sent. This refers to EventBus, so you need to define an Event class before sending messages. It is for standard use and must inherit LiveBusEvent interface, for example:

class FilterSearchEvent(val tag: String, val map: Map<String, Any>) : LiveBusEvent
Copy the code

An activity contains a ViewPager that contains multiple fragments. The activity needs to send a message to the Fragment

Register a listener after Fragemnt onViewCreated, pageEventBus. get must be an Activity or Context, ViewLifecycleOwner is a class that is specific to the Fragment lifecycle. Fragment aware onCreateView and onDestroyView life cycle

PageEventBus.get(requireActivity())
    .of(FilterSearchEvent::class.java)
    .observe(viewLifecycleOwner, Observer {
        DuLogger.d("$mTag FilterSearchEvent: ${it.map}")
        if(it.tag ! = cateKey)return@Observer
        scrollTopRefresh()

    })
Copy the code

Send messages in the Activity

PageEventBus.get(this)
    .post(FilterSearchEvent(tag = filterHelper.getSelectTag(), map = map))
Copy the code

Send messages via eventName

Custom classes are required for each type of event. This may not be necessary in some cases. Here is a way to distinguish messages by event names

PageEventBus.get(this)
            .ofEmpty("pd_refresh_event")
            .observe(this, Observer {
                getProductDetail()
            })
Copy the code

Resend message

PageEventBus.get(this).postEmpty("pd_refresh_event")
Copy the code

As you can see, the difference from custom events is ofEmpty used for registration and postEmpty used for sending

Global communication

Use the same as PageEventBus, but change PageEventBus to LiveEventBus to have the same effect as EventBus

Use skills and precautions

postLatest

When we use LiveData, we find that it provides two methods, setValue and postValue. SetValue can only be used in the main thread, and postValue can be used in any thread, but in fact, PostValue is not just handle.post, we can look at its source code:

protected void postValue(T value) {

    boolean postTask;

    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }

    if(! postTask) {return;
    }

    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);

}
Copy the code

As you can see, mPendingData is assigned each time, but only posted once before processing the task, which prevents multiple meaningless Settings.

In order to prevent confusion LiveEventBus/PageEventBus provides post and postLatest corresponding LiveData setValue and postValue postLatest is LiveData post characteristics of use, If you want to use postLatest continuously, only the last message will be sent to the monitored office. You are advised to use this command for message notification

Such as:

LiveEventBus.get()
    .of(HitEvent::class.java)
    .observe(this, Observer {
        LogUtils.d("LiveEventBus HiltEvent receive: $it")
        doRefresh()
    })

            
Copy the code

// When clicked, multiple events will be sent consecutively and only the last one will be received

LiveEventBus.get().postLatest(HitEvent("HiltEvent 111"))
LiveEventBus.get().postLatest(HitEvent("HiltEvent 222"))
LiveEventBus.get().postLatest(HitEvent("HiltEvent 333"))
Copy the code

Click to send 3 events in a row, only the last one will be received

LiveEventBus HiltEvent receive: HitEvent(content=HiltEvent 333)
Copy the code

Problems with inner classes in Kotlin

In the example above, if the Observer does not have any code related to external classes, it would look like this:

LiveEventBus.get()
    .of(HitEvent::class.java)
    .observe(this, Observer {
        LogUtils.d("LiveEventBus HiltEvent receive: $it")})Copy the code

This listener prints only log, but when multiple Acitivity is open, the following error will be reported when the same code is executed a second time:

Caused by: java.lang.IllegalArgumentException: Please not register same observer com.tory.demo.jetpack.HiltDemoActivity$initView$5@68383e
        at com.tory.library.utils.livebus.BusLiveData.createBusObserver(BusLiveData.java:36)
        at com.tory.library.utils.livebus.BusLiveData.observe(BusLiveData.java:46)
        at com.tory.library.utils.livebus.BusObservableWrapper.observe(BusObservableWrapper.java:63)
        at com.tory.demo.jetpack.HiltDemoActivity.initView(HiltDemoActivity.kt:70)
        at com.tory.library.base.BaseActivity.onCreate(BaseActivity.kt:30)
        at com.tory.demo.jetpack.Hilt_HiltDemoActivity.onCreate(Hilt_HiltDemoActivity.java:29)
        at android.app.Activity.performCreate(Activity.java:7894)
        at android.app.Activity.performCreate(Activity.java:7881)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1307)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3283)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3457) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2044) 
        at android.os.Handler.dispatchMessage(Handler.java:107) 
        at android.os.Looper.loop(Looper.java:224) 
        at android.app.ActivityThread.main(ActivityThread.java:7560) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
Copy the code

Lifecycle is associated with the same Observer as LiveData. Why should the Observer be the same object as LiveData? If it were Java, it would be an anonymous inner class and a new object every time. To find out why, we decompiled Kotlin into Java and you can see that it looks like this

 LiveEventBus.get().of(HitEvent.class).observe((LifecycleOwner)this, (Observer)null.INSTANCE);
Copy the code

As you can see, the Observer is optimized to be a constant INSTANCE!! . But if we add a paragraph in the Observer about the interior of the outer class, we won’t have a problem if

LiveEventBus.get()
    .of(HitEvent::class.java)
    .observe(this, Observer {
        LogUtils.d("LiveEventBus HiltEvent receive: $it $this")})Copy the code

The decompilation looks like this

LiveEventBus.get().of(HitEvent.class).observe((LifecycleOwner)this, (Observer)(new Observer() {
         // $FF: synthetic method
         // $FF: bridge method
         public void onChanged(Object var1) {
            this.onChanged((HitEvent)var1);
         }

         public final void onChanged(HitEvent it) {
            LogUtils.d("LiveEventBus HiltEvent receive: " + it + ' ' + HiltDemoActivity.this); }}));Copy the code

Ultimately, this is due to Kotlin’s optimization of anonymous inner classes, which are unrelated to the outer classes and are optimized to constants.

Listen in the View or ViewHolder

In the View, we usually can not get the corresponding LifecyclerOwner, and if the View is removed, we need to remove the listener. Here also provides methods, such as:

PageEventBus.get(context)
        .of(AddressSelectEvent::class.java)
        .observe(this, Observer {
            currentAddressId = it.addressId
            notifyRefresh()
        })
Copy the code

Observe this is referring to the View itself, it will actually register onViewAttachedToWindow, and unregister onViewDetachedFromWindow

conclusion

Repeating the wheel can be said to be our every development of the road, why to repeat the wheel, ready-made library it does not smell good? On the one hand, our needs will be strange and our wheels can better serve our needs. On the other hand, we can deepen our understanding of certain aspects and learn how to make a good wheel. The specific code is here: github.com/ToryCrox/No…

Reference:

  • Evolution of Android message Bus: Replace RxBus and EventBus with LiveDataBus
  • Android implements EventBus using LiveData