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