background
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 to communicate between Android’s four major components, as well as between asynchronous threads and the main thread. For Android developers, there are a variety of messaging methods commonly used, from the earliest use of Handler, BroadcastReceiver, interface callback, to the popular communication bus framework EventBus, RxBus in recent years. The Android messaging framework is always evolving.
From the 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.
Publish/subscribe
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 emergence of RxBus
RxBus is not a library, but a file, implemented in just 30 lines of code. RxBus itself does not require much analysis; its power comes entirely from the RxJava technology on which it is based. RxJava is the Java implementation of Reactive Programming, a technology that has been particularly popular in recent years. RxJava is a publish/subscribe model by nature and handles thread switching easily. So RxBus challenged EventBus’s dominance with just 30 lines of code.
RxBus principle
In RxJava, there is a Subject class, which inherits Observable class and implements the Observer interface. Therefore, Subject can play the role of both subscriber and subscriber. We use the Subject subclass PublishSubject to create a Subject object (PublishSubject will send events to subscribers immediately only if it is subscribed), subscribe to the Subject object where events are needed, If the Subject object then receives an event, it is transmitted to the subscriber, at which point the Subject object acts as the subscriber.
After the subscription is completed, the event is sent to the previously subscribed Subject object where the event needs to be sent. Then, the Subject object receives the event as the subscriber and immediately forwards the event to the subscriber who subscribes to the Subject object so that the subscriber can process the corresponding event. At this point, the event is sent and processed.
Finally, there is the unsubscription operation. In RxJava, the Subscription operation returns a Subscription object that can be unsubscribed at the appropriate time to prevent memory leaks. If a class produces multiple Subscription objects, We could store a CompositeSubscription for bulk unsubscribes.
RxBus has many implementations, such as:
AndroidKnife/RxBus Blankj/RxBus (https://github.com/AndroidKnife/RxBus) (https://github.com/Blankj/RxBus)
As mentioned above, the principle of RxBus is so simple that we can write our own implementation of RxBus:
RxBus implementation based on RxJava1:
public final class RxBus {
private final Subject<Object, Object> bus;
private RxBus(a) {
bus = new SerializedSubject<>(PublishSubject.create());
}
private static class SingletonHolder {
private static final RxBus defaultRxBus = new RxBus();
}
public static RxBus getInstance(a) {
return SingletonHolder.defaultRxBus;
}
/* * Send */
public void post(Object o) {
bus.onNext(o);
}
/* * Does any Observable subscribe to */
public boolean hasObservable(a) {
return bus.hasObservers();
}
/* * convert to a specific type of Obserbale */
public <T> Observable<T> toObservable(Class<T> type) {
returnbus.ofType(type); }}Copy the code
RxBus implementation based on RxJava2:
public final class RxBus2 {
private final Subject<Object> bus;
private RxBus2(a) {
// toSerialized method made bus thread safe
bus = PublishSubject.create().toSerialized();
}
public static RxBus2 getInstance(a) {
return Holder.BUS;
}
private static class Holder {
private static final RxBus2 BUS = new RxBus2();
}
public void post(Object obj) {
bus.onNext(obj);
}
public <T> Observable<T> toObservable(Class<T> tClass) {
return bus.ofType(tClass);
}
public Observable<Object> toObservable(a) {
return bus;
}
public boolean hasObservers(a) {
returnbus.hasObservers(); }}Copy the code
Introduce the idea of LiveDataBus
From LiveData
LiveData is a framework proposed by Android Architecture Components. LiveData is an observable data holding class that senses and follows the life cycle of components such as activities, fragments, or Services. Because of the component lifecycle awareness nature of LiveData, it is possible to update UI data only when the component is in the active state of the lifecycle.
LiveData requires an Observer object, typically a concrete implementation of the Observer class. LiveData notifies the observer of data changes when the observer’s life cycle is STARTED or RESUMED. LiveData will not be notified even if its data changes while the observer is in another state.
The advantages of LiveData
- The UI is consistent with LiveData because LiveData is in observer mode, so you can be notified when the data changes and update the UI.
- Avoid memory leaks Observers are bound to the life cycle of a component and automatically clean up their own data as soon as the bound component is destroyed.
- Crashes caused by Activity stops are no longer possible. For example, while the Activity is in the background, no events from LiveData are received.
- No longer need to solve the problem of life cycle LiveData is aware of the life cycle of bound components and only notifies data changes when active.
- Real-time data refresh Always receives the latest data when a component is active or inactive to active.
- If the screen is rotated or rebooted, you can receive the latest data immediately.
Talk about Android Architecture Components
Lifecycle, LiveData, ViewModel and Room are the core Components of Android Architecture Components, which can elegantly allow data to interact with the interface, and do some persistent operations, highly decoupled, and automatically manage the Lifecycle. And you don’t have to worry about memory leaks.
- Room is a powerful SQLite object mapping library.
- A class of objects, ViewModel, used to provide data for UI components that can survive device configuration changes.
- LiveData is a life-cycle aware, observable data container that stores data and alerts you when it changes.
- Lifecycle contains LifeCycleOwer and LifecycleObserver, which are the Lifecycle owner and Lifecycle perceiver, respectively.
Features of Android Architecture Components
- Data-driven programming always changes the data, not the interface.
- Aware of the life cycle to prevent memory leaks.
- Highly decoupled data, highly separated interfaces.
- Data persistence data, viewModels are not tied to the life cycle of the UI, and are not destroyed when the interface is rebuilt.
Important: Why use LiveData to build the data communication bus LiveDataBus
Reasons for using LiveData
- LiveData’s observable and life-cycle awareness capabilities make it an ideal building block for Android’s communication bus.
- The consumer does not have to invoke the de-register method explicitly. Because of LiveData’s lifecycle awareness, LiveDataBus only needs to invoke the registration callback method and does not need to invoke the de-registration method shown. This has the advantage of not only writing less code, but also completely eliminating the risk of memory leaks when other communication bus class frameworks (such as EventBus and RxBus) forget to call de-registration.
Why replace EventBus and RxBus with LiveDataBus
- LiveDataBus implementation and its simplicity Compared to the complex implementation of EventBus, LiveDataBus requires only one class to implement.
- LiveDataBus can reduce the size of the APK package because LiveDataBus only relies on the LiveData of the official Android Architecture Components. It has no other dependencies and only one class. For comparison, the EventBus JAR package size is 57kb, and RxBus relies on RxJava and RxAndroid, where the RxJava2 package size is 2.2MB, the RxJava1 package size is 1.1MB, and the RxAndroid package size is 9kb. Using LiveDataBus can greatly reduce the size of APK packages.
- LiveDataBus relies only on LiveData from Android’s official Android Architecture Components, which is better supported than RxJava and RxAndroid that RxBus relies on.
- LiveDataBus is lifecycle aware, using callers in Android systems requires no de-registration, is more convenient than using EventBus and RxBus, and has no memory leak risk.
Design and architecture of LiveDataBus
The composition of LiveDataBus
- Message 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.
- The message channel LiveData plays the role of the message channel. Different message channels are distinguished by different names. The name is a String, and a LiveData message channel can be obtained by the name.
- Message bus The message bus is implemented as a singleton, with different message channels stored in a HashMap.
- The subscriber gets the message channel through getChannel, and then calls Observe to subscribe to the message for that channel.
- The publishing publisher retrieves the message channel through getChannel and publishes the message by calling setValue or postValue.
The principle diagram of the LiveDataBus
The realization of the LiveDataBus
The first implementation:
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
In just twenty lines of code, all the functions of a communication bus are realized, and it also has life-cycle awareness function, and it is extremely simple to use:
Sign up for a subscription:
LiveDataBus.get().getChannel("key_test", Boolean.class)
.observe(this.new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean aBoolean) {}});Copy the code
Send a message:
LiveDataBus.get().getChannel("key_test").setValue(true);
Copy the code
We send an event named “key_test” with a value of true. At this point the subscriber will receive the message and act accordingly, very simply.
Problems arise
For the first version of the LiveDataBus implementation, we found that during the use of the LiveDataBus, subscribers would receive messages published prior to the subscription. This is unacceptable for a message bus. In either EventBus or RxBus, the subscriber will not receive the message sent before the subscription. For a message bus, LiveDataBus has to solve this problem.
Problem analysis
How to solve this problem? First, analyze the reasons:
ActiveStateChanged in LiveData.ObserverWrapper is called when the LifeCircleOwner state changes. If the ObserverWrapper state is active, The dispatchingValue of LiveData is called.
In LiveData’s dispatchingValue, the LiveData considerNotify method is called.
In the LiveData considerNotify method, the logic in the red box is key, and if the mLastVersion of ObserverWrapper is less than the mVersion of LiveData, the mObserver onChanged method is called back. Each new subscriber has a version of -1, and LiveData once set has a version greater than -1 (each LiveData setting increases its version by 1), which causes LiveDataBus to register each new subscriber, The subscriber will receive a callback immediately, even if the action of the setting occurred before the subscription.
Problem Cause Summary
For this problem, summarize the core cause of occurrence. For LiveData, its initial version is -1. When we call its setValue or postValue, its vesion will be +1. The initial version of the ObserverWrapper wrapper for each observer is also -1, that is, each newly registered observer has a version of -1. When LiveData sets the ObserverWrapper, if the version of LiveData is greater than the version of ObserverWrapper, LiveData forces the current value to be pushed to the Observer.
How to solve this problem
Now that we understand the cause of the problem, let’s see how we can solve it. Obviously, based on the previous analysis, you only need to set the Wrapper version to the version of LiveData when registering a new subscriber.
To do that, look at the Observe method of LiveData, which creates a LifecycleBoundObserver in Step 1, which is a derivative of ObserverWrapper. The LifecycleBoundObserver is then put into a private Map container, mObservers, in Step 2. Both ObserverWrapper and LifecycleBoundObserver are private or package visible, so there is no way to change the version of LifecycleBoundObserver by inheritance.
Can I fetch LifecycleBoundObserver from the Map mObservers and then change the version? The answer is yes. Looking at the SafeIterableMap source code, we found that there is a protected GET method. Therefore, when calling Observe, we can get LifecycleBoundObserver through reflection and set the version of LifecycleBoundObserver to be the same as that of LiveData.
For the non-lifecycle aware observeForever method, the idea is the same, but the implementation is slightly different. The wrapper generated when observeForever is not LifecycleBoundObserver, but AlwaysActiveObserver (Step 1). And we don’t have a chance to change the version of the AlwaysActiveObserver until after the observeForever call is complete, because the callback occurs in step 3 of the observeForever method.
So for observeForever, how to solve this problem? Since the callback is inside the call, we can write an ObserverWrapper to wrap the real callback. Pass the ObserverWrapper to observeForever, then we check the call stack at callback time, if the callback is caused by the observeForever method, then the true subscriber is not called back.
LiveDataBus is finally implemented
public final class LiveDataBus {
private final Map<String, BusMutableLiveData<Object>> bus;
private LiveDataBus(a) {
bus = new HashMap<>();
}
private static class SingletonHolder {
private static final LiveDataBus DEFAULT_BUS = new LiveDataBus();
}
public static LiveDataBus get(a) {
return SingletonHolder.DEFAULT_BUS;
}
public <T> MutableLiveData<T> with(String key, Class<T> type) {
if(! bus.containsKey(key)) { bus.put(key,new BusMutableLiveData<>());
}
return (MutableLiveData<T>) bus.get(key);
}
public MutableLiveData<Object> with(String key) {
return with(key, Object.class);
}
private static class ObserverWrapper<T> implements Observer<T> {
private Observer<T> observer;
public ObserverWrapper(Observer<T> observer) {
this.observer = observer;
}
@Override
public void onChanged(@Nullable T t) {
if(observer ! =null) {
if (isCallOnObserve()) {
return; } observer.onChanged(t); }}private boolean isCallOnObserve(a) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
if(stackTrace ! =null && stackTrace.length > 0) {
for (StackTraceElement element : stackTrace) {
if ("android.arch.lifecycle.LiveData".equals(element.getClassName()) &&
"observeForever".equals(element.getMethodName())) {
return true; }}}return false; }}private static class BusMutableLiveData<T> extends MutableLiveData<T> {
private Map<Observer, Observer> observerMap = new HashMap<>();
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
super.observe(owner, observer);
try {
hook(observer);
} catch(Exception e) { e.printStackTrace(); }}@Override
public void observeForever(@NonNull Observer<T> observer) {
if(! observerMap.containsKey(observer)) { observerMap.put(observer,new ObserverWrapper(observer));
}
super.observeForever(observerMap.get(observer));
}
@Override
public void removeObserver(@NonNull Observer<T> observer) {
Observer realObserver = null;
if (observerMap.containsKey(observer)) {
realObserver = observerMap.remove(observer);
} else {
realObserver = observer;
}
super.removeObserver(realObserver);
}
private void hook(@NonNull Observer<T> observer) throws Exception {
//get wrapper's version
Class<LiveData> classLiveData = LiveData.class;
Field fieldObservers = classLiveData.getDeclaredField("mObservers");
fieldObservers.setAccessible(true);
Object objectObservers = fieldObservers.get(this); Class<? > classObservers = objectObservers.getClass(); Method methodGet = classObservers.getDeclaredMethod("get", Object.class);
methodGet.setAccessible(true);
Object objectWrapperEntry = methodGet.invoke(objectObservers, observer);
Object objectWrapper = null;
if (objectWrapperEntry instanceof Map.Entry) {
objectWrapper = ((Map.Entry) objectWrapperEntry).getValue();
}
if (objectWrapper == null) {
throw new NullPointerException("Wrapper can not be bull!"); } Class<? > classObserverWrapper = objectWrapper.getClass().getSuperclass(); Field fieldLastVersion = classObserverWrapper.getDeclaredField("mLastVersion");
fieldLastVersion.setAccessible(true);
//get livedata's version
Field fieldVersion = classLiveData.getDeclaredField("mVersion");
fieldVersion.setAccessible(true);
Object objectVersion = fieldVersion.get(this);
//set wrapper's versionfieldLastVersion.set(objectWrapper, objectVersion); }}}Copy the code
Register subscriptions
LiveDataBus.get()
.with("key_test", String.class)
.observe(this.new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {}});Copy the code
Send a message:
LiveDataBus.get().with("key_test").setValue(s);
Copy the code
The source code that
LiveDataBus source can directly copy to use, also can be downloaded to the author’s making warehouse check: https://github.com/JeremyLiao/LiveDataBus
conclusion
This article provides a new message bus framework, LiveDataBus. A subscriber can subscribe to a message channel, and a publisher can publish messages to a message channel. With LiveDataBus, not only can message bus capabilities be implemented, but subscribers do not need to care when to unsubscribe, greatly reducing the risk of memory leaks due to forgetting to unsubscribe.
The authors introduce
Hai Liang, a senior engineer of Meituan, joined Meituan in 2017. Currently, he is mainly responsible for the business and module development of Meituan Light Cashier, Meituan Retail cashier and other apps.