This article is published by the Cloud + community
Implementation of event bus core logic.
The role of the EventBus
There are various communication scenarios in Android, such as the jump between activities, the interaction between activities and fragments, and other components, as well as the callback after a time-consuming operation (such as network request), which often need to hold each other’s reference. Each scenario is also written differently, resulting in high coupling and poor maintenance. Take the communication between activities and fragments as an example. The official practice is to implement an interface, hold the reference of the other side, and then forcibly change it to the interface type, resulting in high coupling degree. Taking the return of an Activity as an example, one side needs to set setResult, and the other side needs to do the corresponding processing in onActivityResult. If there are multiple return paths, the code will be very bloated. SimpleEventBus (the simplified version of the event bus that this article eventually implemented) is written as follows:
public class MainActivity extends AppCompatActivity {
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.tv_demo);
mTextView.setText("MainActivity");
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent); }}); EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onReturn(Message message) {
mTextView.setText(message.mContent);
}
@Override
protected void onDestroy(a) {
super.onDestroy();
EventBus.getDefault().unregister(this); }}Copy the code
Source of the Activity:
public class SecondActivity extends AppCompatActivity {
TextView mTextView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.tv_demo);
mTextView.setText("SecondActivity, click back");
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Message message = new Message();
message.mContent = "Return from SecondActivity"; EventBus.getDefault().post(message); finish(); }}); }}Copy the code
The effect is as follows:
It may seem like just another way to write it, but as the scenario becomes more complex, EventBus can be better decoupled.
Background knowledge
It mainly involves three aspects of knowledge:
-
Observer mode (or publish-subscribe mode)
-
Android messaging
-
Java concurrent programming
This article is a guide to the source code of the GreenRobot /EventBus open source library. I learned about this library while reading books on design patterns and felt it was necessary to implement the core functions to deepen my understanding.
The implementation process
The use of EventBus consists of three steps: registering a listener, sending an event, and canceling a listener.
Register to monitor
Define a comment:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POST;
}
Copy the code
Greenrobot /EventBus also supports priority and sticky events, but only the most basic capability is supported here: thread segmentation, since actions such as UI updates must be placed on the main thread. ThreadMode is as follows:
Public enum ThreadMode {MAIN, ASYNC, ASYNC} public enum ThreadMode {MAIN, ASYNC, ASYNC}Copy the code
When the object is initialized, the register method is used. This method resolves all the methods of the registered object and resolves the methods that declare annotations (i.e. observers). The core code is as follows:
public class EventBus {
...
public void register(Object subscriber) {
if (subscriber == null) {
return; } synchronized (this) { subscribe(subscriber); }}... private void subscribe(Object subscriber) {if (subscriber == null) {
return; } // TODO ugly indent Class<? > clazz = subscriber.getClass();while(clazz ! = null && ! isSystemClass(clazz.getName())) { final Method[] methods = clazz.getDeclaredMethods();for (Method method : methods) {
Subscribe annotation = method.getAnnotation(Subscribe.class);
if(annotation ! = null) { Class<? >[] paramClassArray = method.getParameterTypes();if (paramClassArray != null && paramClassArray.length == 1) {
Class<?> paramType = convertType(paramClassArray[0]);
EventType eventType = new EventType(paramType);
SubscriberMethod subscriberMethod = new SubscriberMethod(method, annotation.threadMode(), paramType);
realSubscribe(subscriber, subscriberMethod, eventType);
}
}
}
clazz = clazz.getSuperclass();
}
}
...
private void realSubscribe(Object subscriber, SubscriberMethod method, EventType eventType) {
CopyOnWriteArrayList<Subscription> subscriptions = mSubscriptionsByEventtype.get(subscriber);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
}
Subscription subscription = new Subscription(subscriber, method);
if (subscriptions.contains(subscription)) {
return; } subscriptions.add(subscription); mSubscriptionsByEventtype.put(eventType, subscriptions); }... }Copy the code
Once this logic is done, all observer methods of the object are stored in a Map. The Key is EventType, which is the type of the observed event, and the Value is a list of all methods (observers) subscribed to that type of event. Each method and object is encapsulated in a Subscription class:
public class Subscription { public final Reference<Object> subscriber; public final SubscriberMethod subscriberMethod; public Subscription(Object subscriber, SubscriberMethod subscriberMethod) { this.subscriber = new WeakReference<>(subscriber); // EventBus3 does not have weak references? this.subscriberMethod = subscriberMethod; } @Override public inthashCode() {
return subscriber.hashCode() + subscriberMethod.methodString.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Subscription) {
Subscription other = (Subscription) obj;
return subscriber == other.subscribe
&& subscriberMethod.equals(other.subscriberMethod);
} else {
return false; }}}Copy the code
This is the core logic for registering listener methods.
Message is sent
The code to send the message is simple:
public class EventBus {
...
private EventDispatcher mEventDispatcher = new EventDispatcher();
private ThreadLocal<Queue<EventType>> mThreadLocalEvents = new ThreadLocal<Queue<EventType>>() {
@Override
protected Queue<EventType> initialValue() {
returnnew ConcurrentLinkedQueue<>(); }}; . public void post(Object message) {if (message == null) {
return; } mThreadLocalEvents.get().offer(new EventType(message.getClass())); mEventDispatcher.dispatchEvents(message); }... }Copy the code
A bit more complicated is the need to publish in the corresponding thread according to the thread mode declared by the annotation:
public class EventBus {
...
private class EventDispatcher {
private IEventHandler mMainEventHandler = new MainEventHandler();
private IEventHandler mPostEventHandler = new DefaultEventHandler();
private IEventHandler mAsyncEventHandler = new AsyncEventHandler();
void dispatchEvents(Object message) {
Queue<EventType> eventQueue = mThreadLocalEvents.get();
while (eventQueue.size() > 0) {
handleEvent(eventQueue.poll(), message);
}
}
private void handleEvent(EventType eventType, Object message) {
List<Subscription> subscriptions = mSubscriptionsByEventtype.get(eventType);
if (subscriptions == null) {
return;
}
for (Subscription subscription : subscriptions) {
IEventHandler eventHandler = getEventHandler(subscription.subscriberMethod.threadMode);
eventHandler.handleEvent(subscription, message);
}
}
private IEventHandler getEventHandler(ThreadMode mode) {
if (mode == ThreadMode.ASYNC) {
return mAsyncEventHandler;
}
if (mode == ThreadMode.POST) {
return mPostEventHandler;
}
return mMainEventHandler;
}
}// end of the class
...
}
Copy the code
The three thread modes are as follows: DefaultEventHandler (executes the observer drop method on the publish thread) :
public class DefaultEventHandler implements IEventHandler { @Override public void handleEvent(Subscription subscription, Object message) {if (subscription == null || subscription.subscriber.get() == null) {
return; } try { subscription.subscriberMethod.method.invoke(subscription.subscriber.get(), message); } catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }}}Copy the code
MainEventHandler (executed on the main thread) :
public class MainEventHandler implements IEventHandler {
private Handler mMainHandler = new Handler(Looper.getMainLooper());
DefaultEventHandler hanlder = new DefaultEventHandler();
@Override
public void handleEvent(final Subscription subscription, final Object message) {
mMainHandler.post(new Runnable() {
@Override
public void run() { hanlder.handleEvent(subscription, message); }}); }}Copy the code
AsyncEventHandler (starts a new thread to execute) :
public class AsyncEventHandler implements IEventHandler {
private DispatcherThread mDispatcherThread;
private IEventHandler mEventHandler = new DefaultEventHandler();
public AsyncEventHandler() {
mDispatcherThread = new DispatcherThread(AsyncEventHandler.class.getSimpleName());
mDispatcherThread.start();
}
@Override
public void handleEvent(final Subscription subscription, final Object message) {
mDispatcherThread.post(new Runnable() {
@Override
public void run() { mEventHandler.handleEvent(subscription, message); }}); } private class DispatcherThread extends HandlerThread {// Handle Handler extends HandlerThread; DispatcherThread(String name) { super(name); } public void post(Runnable runnable) {if (mAsyncHandler == null) {
throw new NullPointerException("mAsyncHandler == null, please call start() first.");
}
mAsyncHandler.post(runnable);
}
@Override
public synchronized void start() { super.start(); mAsyncHandler = new Handler(this.getLooper()); }}}Copy the code
This is the code to publish the message.
Cancellation of listening
At present, SimpleEventBus uses WeakReference, which can be recovered automatically through GC, but I don’t know why GreenRobot /EventBus is not implemented in this way, which needs to be studied. To unsubscribe a listener is to traverse the Map and unsubscribe the object’s subscription:
public class EventBus {
...
public void unregister(Object subscriber) {
if (subscriber == null) {
return;
}
Iterator<CopyOnWriteArrayList<Subscription>> iterator = mSubscriptionsByEventtype.values().iterator();
while (iterator.hasNext()) {
CopyOnWriteArrayList<Subscription> subscriptions = iterator.next();
if(subscriptions ! = null) { List<Subscription> foundSubscriptions = new LinkedList<>();for (Subscription subscription : subscriptions) {
Object cacheObject = subscription.subscriber.get();
if(cacheObject == null || cacheObject.equals(subscriber)) { foundSubscriptions.add(subscription); } } subscriptions.removeAll(foundSubscriptions); } // If the number of subscribers for an Event is empty, it needs to be removed from the mapif(subscriptions == null || subscriptions.size() == 0) { iterator.remove(); }}}... }Copy the code
The above is the core part of the event bus code implementation, the complete code see Vimerzhao /SimpleEventBus, later found problems update or upgrade will only change the repository code.
limitations
Due to time constraints, only the core part of EventBus has been studied so far. There are still several points worthy of further study, which are recorded here.
Performance issues
In practice, annotations and reflection can cause performance problems, but EventBus3 has basically solved this problem with the Subscriber Index, which is an interesting implementation that uses the Annotation Processor to move the time-consuming logic from run time to compile time. The name is derived from the use of compile-time generated indexes to speed up runtime.
Usability issues
Will it affect the experience if there are a lot of subscribers? After all, the original method is point-to-point messaging and there is no such problem. What if some subscribers are not already initialized? And so on. EventBus3 now provides priority and sticky event attributes to further meet development needs. But it remains to be seen whether the problem is solved.
Cross-process problem
EventBus is an in-process message management mechanism, and the project has been very successful from the feedback of the open source community. However, it is worth exploring whether it is necessary to support multiple processes and provide the same code decoupling capability while maintaining performance. Currently lsxiao/Apollo and Xiaofei-it/HermesEventBus are available for reference.
reference
-
greenrobot/EventBus
-
hehonghui/AndroidEventBus
-
Chapter 10 in Exploring the Art of Android Development
This article has been published by Tencent Cloud + community authorized by the author