Bugly technology dry goods series content mainly involves the direction of mobile development, is invited by Bugly Tencent technology masters, through the summary of daily work experience and perception, the content is original, please indicate the source of reprint.

EventBus, which will be familiar to Android developers, is an observer-based event publish/subscribe framework that allows you to communicate with multiple modules in very little code, rather than having to build a separate communication bridge in the form of layers of delivery interfaces. This reduces the strong coupling between modules caused by multiple callbacks while avoiding the creation of a large number of inner classes. It has the advantages of easy to use, high performance, low access cost and support multithreading, which is a necessary medicine for module decoupling and code reconstruction.

As the star component of The Github 9000+ Star, which Markus Junginger spent four years working on and has over 100 million connections, there have been countless articles analyzing EventBus. The topic of this article is “teach you up Bus”, and the reason why the Bus can be soaring up, because the author introduces in the EventBus 3 EventBusAnnotationProcessor (annotation analysis index generated) technology, greatly improving the efficiency of EventBus. In this article, we will analyze the new features of EventBus 3, share some experiences with you, and learn how to use and operate EventBus 3 by using source code analysis and UML diagrams.

1. Beginners on the road — Use EventBus

1.1 Importing Components

Add the latest EventBus dependencies to your dependencies:

The compile 'org. Greenrobot: eventbus: 3.0.0'Copy the code

If you don’t need index acceleration, you can skip to step 2. And to apply the latest EventBusAnnotationProcessor is more troublesome, because annotations parsing depends on the android – apt – the plugin. Add apt to the dependencies of the project gradle.

The classpath 'com. Neenbedankt. Gradle. Plugins: android - apt: 1.8'Copy the code

Build. Gradle (apt); build. Gradle (apt);

apply plugin: 'com.neenbedankt.android-apt'
apt {
    arguments {
        eventBusIndex "com.study.sangerzhong.studyapp.MyEventBusIndex"
    }
}Copy the code

Then introduce EventBusAnnotationProcessor dependencies in the App:

Apt 'org. Greenrobot: eventbus -- the annotation processor: 3.0.1'Copy the code

To note here, if the application EventBusAnnotationProcessor but do not set the arguments, compile time would be an error: No option eventBusIndex passed to the annotation processor

At this point, we need to compile once to generate the index class. After a successful compilation, you will see the index class generated by annotation analysis under \ProjectName\app\build\generated\source\apt\PakageName\ so that we can apply our generated index when initializing EventBus.

1.2 Initializing EventBus

EventBus has a singleton by default, which can be retrieved from getDefault(), or a custom EventBus can be constructed from eventbus.Builder (), for example, to apply our generated index:

EventBus mEventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();Copy the code

If you want to apply custom Settings to the default singleton of EventBus, use the installDefaultEventBus() method:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();Copy the code

1.3 Defining Events

All instances that can be instantiated as objects can be treated as events:

public class DriverEvent { public String info; }Copy the code

In the latest version of EventBus 3, if index acceleration is used, the modifier of the event class must be public, otherwise an error will be reported during compilation: Subscriber method must be public.

1.4 Listening Events

First, register the module as a subscription event to listen via EventBus:

mEventBus.register(this);Copy the code

Prior to 3.0, registered listeners needed to distinguish whether they were listening for sticky events, and modules listening for EventBus events needed to implement methods starting with onEvent. This is now in the form of an annotated method:

@Subscribe(threadMode = ThreadMode.POSTING, priority = 0, sticky = true)
public void handleEvent(DriverEvent event) {
    Log.d(TAG, event.info);
}Copy the code

The annotation has three parameters, threadMode is the thread in which the callback occurs, priority is the priority, and sticky is whether to receive sticky events. The scheduling unit is refined from class to method, and there is no requirement for method naming, which is convenient to confuse the code. The module that registers a listener must have a Subscribe annotation method, otherwise the register will throw an exception:

Subscriber class XXX and its super classes have no public methods with the @Subscribe annotationCopy the code

1.5 Sending Events

Call post or postSticky:

mEventBus.post(new DriverEvent("magnet:? Xt = urn: btih..." ));Copy the code

At this point, we’re done learning how to use EventBus, and we’re free to drive around in code. What are the benefits of having access to EventBus? A common use case is that fragments in a ViewPager communicate with each other. Instead of defining interfaces in the container, callbacks can be implemented directly through EventBus, which takes the logic out of the ViewPager container and makes the code more intuitive to read.

Threadmode. MainThread is a good way to solve the problem that Android interface refreshes must be done in the UI thread. Sticky events can solve the asynchronous problem of simultaneous post and Register execution, and there is no performance cost of serialization and deserialization. Enough to meet our modular communication requirements in most cases.

2. Become an old driver — Analysis of EventBus Principle

We don’t need to worry about EventBus’s event distribution mechanism in normal use, but we need to be familiar with how it works in order to become an experienced driver who can quickly troubleshoot problems. Let’s take a look at it using UML diagrams.

2.1 Core Architecture

The core workings of EventBus can be best understood by looking at this diagram from the author’s Blog:

The subscriber module subscribes to events via EventBus and has callback methods ready to handle them, while the event publisher posts the event when appropriate, and EventBus takes care of it for us. In terms of architecture, EventBus 3 differs from older versions, so let’s go straight to the architecture diagram:

Let’s look at the core class EventBus, where subscriptionByEventType is a mapping table where the event class is the key and the subscriber’s callback method is value. That is, when EventBus receives an event, it can find in subscriptionByEventType all the subscribers that listened for the event and the callback methods that handled the event, based on the event type. TypesBySubscriber is a table of event types monitored by each subscriber. When canceling the registration, the subscriber’s registration information in subscriptionByEventType can be quickly deleted through the information saved in the table to avoid traversal search. Registering events, sending events, and unregistering all revolve around these two core data structures. The Subscription above can be understood as a relationship between each subscriber and the callback method, which the subscriber executes when other modules send events.

The callback method is encapsulated here as SubscriptionMethod, which holds the parameters needed to reflect the Invoke method, including priority, whether it receives sticky events, and the thread in which it is located. To generate these wrapped methods, you need the SubscriberMethodFinder, which gets all of the subscriber’s callback methods at regster and wraps them back to EventBus. The accelerator module on the right, which is designed to improve the efficiency of SubscriberMethodFinder, will be described in detail in chapter 3 without further ado.

The following three posters are the core of EventBus’s ability to execute callback methods in different threads.

  1. POSTING (callback performed in the thread where the POST was called) : Don’t need poster to schedule, run directly.

  2. MAIN (callback in the UI thread) : The post is executed directly if the thread is UI, otherwise it is scheduled via mainThreadPoster.

  3. BACKGROUND (callback in the Backgroud thread) : The post is executed directly if the thread is not a UI thread, otherwise it is scheduled by backgroundPoster.

  4. ASYNC (to be managed by the thread pool) : Scheduled directly via asyncPoster.

As you can see, different posters schedule the corresponding event queue PendingPostQueue at post events so that each subscriber’s callback method receives the corresponding event and runs it in its registered Thread. The event queue is a linked list composed of PendingPost, which contains the event, event subscriber, callback method three core parameters, and the next PendingPost to be executed.

At this point, the architecture of EventBus 3 is analyzed. The most obvious difference from the previous version of EventBus is that the dispatch unit of events is refined from the subscriber to the subscriber’s callback method. This means that each callback method has its own priority, thread of execution, and whether or not it receives sticky events, which increases the flexibility of event distribution. This will be more apparent when we look at the implementation of the core functionality.

2.2 the register

In simple terms: retrieve callback methods according to the subscriber’s class, encapsulate the subscriber and callback methods into a relationship, and save it in the corresponding data structure, prepare for the subsequent event distribution, and finally handle sticky events:

Note that older versions of EventBus allow event subscribers to listen for the same event in different threadModes. In this case, there is no guarantee of the order of callbacks because different thread callbacks are scheduled by Handler. It is possible that there are too many events in a single thread, events are blocked, and callbacks are slow. EventBus 3 now uses annotations to represent callbacks, and the same ThreadMode callback method can listen for the same event. In this case, the event will be registered before it is sent, depending on the order in which the event is registered.

2.3 post

To summarize, we analyze the event, get the callback methods of all subscribers listening on the event, and use reflection to invoke the callback method:

Here you can see the scheduling event capability for Poster, and you can see that the scheduling unit is refined to Subscription, where each method has its own priority and whether or not to receive sticky events. In the source code to ensure that post execution does not deadlock, wait and send the same event to the same subscriber, added a lot of thread-protected locks and flag bits, worth every developer to learn.

2.4 the unregister

Unregister is relatively simple, delete the subscriber information added to the two data structures during registration:

Sticky events are often mentioned above, why do we have this design? Here’s an example: I want the song to play automatically after a successful login, and login and listen login are simultaneous. Under this premise, if the login process goes very fast, the playback module will register the listener only after the login succeeds. At this point, the playback module will miss the event of “login success”, and the situation will appear: “Although the login is successful, the callback is not executed”. If the event is a sticky event, then even if I register the listener later (and the callback method is set to listen for sticky events), the callback can be executed at the moment of registration, and the problem can be solved elegantly without having to define additional flag bits.

At this point you the right EventBus works should have a certain understanding, although looks like a complicated time consuming automata, but most of the time the event is a flash can distribute in place, and everyone concerned about the performance of the problem is happen when registered EventBus instead, because of the need to traverse the listener all the way to find the callback methods. The author also mentions that runtime annotations do not perform well on Android, and to address this issue, the author uses an indexed callback method table (more on that in the next chapter). EventBus3 is a class diagram and flow chart that provides an overview of EventBus3’s architecture and core functions. Leave the source code analysis to introduce EventBusAnnotationProcessor behind. If you want to learn more about EventBus 3, there’s a lot of well-written source code analysis in the reference article at the end of this article.

3. Turbo Engine — Index acceleration

In the introduction to EventBus 3, the authors mentioned that previous versions used reflection rather than annotations when iterating through callbacks to find subscribers to ensure performance. But now it’s possible to dramatically improve performance with annotations, and the author posted this comparison on his blog:

You can see that in terms of performance, EventBus 3 lags behind version 2.4, which uses reflection to traverse methods, due to its use of annotations. However, when indexing is enabled, the performance is much better than the previous version. Here’s a look at the “turbo engine” that improves EventBus performance. (The source code analysis below has been annotated and truncated for ease of reading. If in doubt, check out the original source code on Github.)

The index is passed in via the EventBusBuilder.addIndex(SubscriberInfoIndex index) method when initializing EventBus.

public EventBusBuilder addIndex(SubscriberInfoIndex index) { if(subscriberInfoIndexes == null) { subscriberInfoIndexes =  new ArrayList<>(); } subscriberInfoIndexes.add(index); return this; }Copy the code

As you can see, the index information that is passed in is stored in the subscriberInfoIndexes List, which is then passed through EventBusBuilder to the SubscriberMethodFinder instance of the corresponding EventBus. Let’s first examine the SubscriberInfoIndex parameter:

public interface SubscriberInfoIndex {
    SubscriberInfo getSubscriberInfo(Class subscriberClass);
}Copy the code

The visible index only needs to do one thing – that is, get the subscriber’s information. The class that implements this interface would not be found if we hadn’t compiled it. Here will have to see when we are in the first place in the configuration gradle import EventBusAnnotationProcessor:

@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe") @SupportedOptions(value = {"eventBusIndex", "verbose"}) public class EventBusAnnotationProcessor extends AbstractProcessor { /** Found subscriber methods for a class (without superclasses). */ private final ListMap methodsByClass = new ListMap<>(); private final Set classesToSkip = new HashSet<>(); } // checkHasErrors @override public Boolean process(Set Annotations, RoundEnvironment env) { Messager messager = processingEnv.getMessager(); try { String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX); If (index == null) {// If apt argument is not configured in Gradle PrintMessage (diagnostic.kind. ERROR, "No option " + OPTION_EVENT_BUS_INDEX + " passed to annotation processor"); return false; } / * *... */ collectSubscribers(annotations, env, messager); / / callback methods information according to the annotation to all subscribers checkForSubscribersToSkip (messenger, indexPackage); // filter out non-conforming subscribers if (! methodsByClass.isEmpty()) { createInfoIndexFile(index); Size of annotations */ Private void collectSubscribers // Annotations, size of annotations MethodsByClass private Boolean checkHasNoErrors static, The public and the method of parameter is greater than 1 private void checkForSubscribersToSkip methodsByClass / / inspection of each class, Whether there is a parent class and methods of the non-public parameters / * * the information in the following three methods would make methodsByClass wrote the corresponding class * / private void writeCreateSubscriberMethods private void createInfoIndexFile private void writeIndexLines }Copy the code

At this point it uncover the secrets of index generated, it is apt at compile time plugin by analyzing EventBusAnnotationProcessor annotations, and use the annotation identifies the relevant information to generate the relevant class. WriteCreateSubscriberMethods invoked in a lot of IO function, it is easy to understand, here is not posted, we generated directly see class:

/** This class is generated by EventBus, do not edit. */ public class MyEventBusIndex implements SubscriberInfoIndex { private static final Map, SubscriberInfo> SUBSCRIBER_INDEX; static { SUBSCRIBER_INDEX = new HashMap, SubscriberInfo>(); // For each subscriber class, Call a putIndex relevant information is added to the index putIndex (new SimpleSubscriberInfo (com. Study. Sangerzhong. Studyapp. UI. MainActivity. Class, true, new SubscriberMethodInfo[] { new SubscriberMethodInfo("onEvent", com.study.sangerzhong.studyapp.ui.MainActivity.DriverEvent.class, ThreadMode.POSTING, 0, false), // Each of the subscribed methods in the class is added here})); } / / the following code is written in EventBusAnnotationProcessor died of the private static void putIndex (SubscriberInfo info) { SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info); } @Override public SubscriberInfo getSubscriberInfo(Class subscriberClass) { SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass); if (info ! = null) { return info; } else { return null; }}}Copy the code

Hardcode is a subclass of hardCode that identifies methods with the Subscribe annotation in all classes registered with EventBus, including method names, method parameter types, etc. Encapsulating this information into SimpleSubscriberInfo, we get an index that is essentially a hash table with the subscriber’s class as Key and SimpleSubscriberInfo as value. These hardcodes are generated at compile time, eliminating the need to iterate through the build at eventbus.register (), thus moving up the need to iterate through all of the subscriber’s methods at register time to compile time.

Now that we know about index generation, where is it applied? Remember that at registration time, EventBus iterates through all the methods of the Class of the registered object through the SubscriberMethodFinder, screening out methods that conform to the rules, and acts as a callback that the subscriber performs when it receives an event. We directly to see SubscriberMethodFinder. FindSubscriberMethods () method of the core:

List findSubscriberMethods(Class subscriberClass) { List subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods ! = null) { return subscriberMethods; } if (ignoreGeneratedIndex) {// Whether to ignore the set index. } else { subscriberMethods = findUsingInfo(subscriberClass); } /** Save the found method to METHOD_CACHE and return it.Copy the code

The findUsingInfo() method is a callback to find the subscriber in the index.

Private List findUsingInfo(Class subscriberClass) { All temporary variables needed to find a method are encapsulated in a static inner class called FindState. FindState FindState = prepareFindState(); / / to the context object in the pool, avoid frequent create objects, the design is very great findState. InitForSubscriber (subscriberClass); // Initialize the context for finding the method while (findstate.clazz! Findstate. subscriberInfo = getSubscriberInfo(findState); If (findState.subscriberInfo! = null) {/ / step can get related information, began to encapsulate methods array into the List SubscriberMethod [] array. = findState subscriberInfo. GetSubscriberMethods (); for (SubscriberMethod subscriberMethod : array) { if (findState.checkAdd(subscriberMethod.method, SubscriberMethod. EventType)) {/ / checkAdd is to avoid found in the parent class method is to be subclasses override, At this time should ensure that the callback when a subclass methods findState. SubscriberMethods. Add (subscriberMethod); }}} else {/ / index, the drop into the runtime through the notes and reflection to find findUsingReflectionInSingleClass (findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); // Release FindState into the object pool and return the found callback method}Copy the code

You can see that EventBus handles inheritance when looking for subscriber callback methods, not only traversing the parent class, but also avoiding multiple callbacks due to overwriting methods. What you need to care about is how getSubscriberInfo() returns the index data. Let’s dig deeper:

private SubscriberInfo getSubscriberInfo(FindState findState) { if (findState.subscriberInfo ! = null && findState.subscriberInfo.getSuperSubscriberInfo() ! = null) {// subscriberInfo Prove that the last time the need to search to find the class parent SubscriberInfo superclassInfo = findState. SubscriberInfo. GetSuperSubscriberInfo (); If (findState. Clazz = = superclassInfo. GetSubscriberClass ()) {/ / is required for the return superclassInfo; } } if (subscriberInfoIndexes ! = null) {// Get the corresponding subscriber information from the indexes that we sent in. subscriberInfoIndexes) { SubscriberInfo info = index.getSubscriberInfo(findState.clazz); if (info ! = null) { return info; } } } return null; }Copy the code

Visible in this method is the index we generated, avoiding the need to call the time-consuming findUsingReflection method when we findSubscriberMethods. In practice, it takes about 10 milliseconds for an Activity to register EventBus on Nexus6, but with indexes it can be reduced to about 3 milliseconds, which is quite impressive. Although the implementation logic of this index is a bit convoluted and there are some pitfalls (such as confusion later), the implementation is very clever, especially the idea of “doing time-consuming operations at compile time” and “using object pooling to reduce the performance overhead of creating objects”.

4. Driving treasure book — pits and experience

4.1 Confusion

Obfuscation is a necessary part of the release process, and it often leads to strange problems that are difficult to locate, especially for libraries like EventBus that rely on reflection technology. It is common to keep all classes and callback methods hidden, but this leaves the possibility of decompiling and cracking, so the goal is to keep the minimum code.

First, because EventBus 3 abandons reflection to find callback methods and uses annotations instead. The author means that there is no need to keep the corresponding classes and methods when confused. But we at run time, but will the Java lang. NoSuchFieldError: No static field POSTING. The solution is to keep all eventBus-related code:

-keep class de.greenrobot.** {*; }Copy the code

When calling method.getannotation (), the enum ThreadMode failed to be obtained. So we just need to keep the enum (see below).

-keep public enum org.greenrobot.eventbus.ThreadMode { public static *; }Copy the code

This should compile normally, but if you use index acceleration, you won’t have this problem. Because instead of calling findUsingReflection when finding a method, you call findUsingInfo. Could not find subscriber method in XXX Class. Maybe a missing ProGuard rule?

Subscriberindex () {Subscribe (); Subscribe (); Subscribe (); Subscribe (); Subscribe (); Subscribe (); Subscribe ();

-keepclassmembers class * {
    @de.greenrobot.event.Subscribe ;
}Copy the code

So it’s back to EventBus2.4 where you can’t confuse the start of an onEvent. So it’s a tradeoff: if you use annotations instead of index acceleration, you just need to keep the EventBus-related code, and the existing code will obfuscate normally. With index acceleration, you need to keep relevant methods and classes.

4.2 Cross-process problems

EventBus currently supports only cross-threads, not cross-processes. If one app’s service is in another process, the module registered to listen will not receive events from the other process’s EventBus. In this case, we can consider using IPC to make the mapping table and maintain an EventBus in each of the two processes. However, it is cumbersome to maintain the relationship between register and unregister by ourselves. In this case, it is usually more convenient to use broadcast, so we can consider whether there is a better solution.

4.3 Event loop Problem

When using EventBus, it is common for two modules to listen to each other for callback communication. However, once an infinite loop occurs, and without the corresponding log information, it is difficult to locate the problem. For modules that use EventBus, if there is a loop on the callback and the callback method is complex enough, consider encapsulating the received event into a submodule and avoid event loops.

5. The Road to car God — last

Of course, EventBus isn’t the only option for refactoring code. As the observer mode’s “brother” — RxJava, as a more powerful responsive programming framework, can easily implement EventBus function (RxBus). However, RxJava is expensive for large projects, and complex operators require more time to learn. For fast refactoring and decoupling of modules in mature projects, EventBus is still the place to go.

<

This article summarizes how to use EventBus 3, how it works, and some of the new features, so you can see the pros and cons of this component and have a good idea when considering whether to include EventBus in your project. Finally, I would like to thank Markus Junginger for open source such a practical component, as well as colleagues in the team who provided help when I explored the principle of EventBus. I hope that after reading this article, you can learn something and become NB Android development veteran drivers.

reference

Tencent Bugly is a quality control tool designed for mobile developers, helping developers quickly and easily locate online app crashes and solutions. The intelligent merging function helps developers classify thousands of crashes reported every day according to root causes, and the daily daily lists the crashes that affect the most users. The precise positioning function helps developers locate the lines of code with problems, and real-time reporting enables them to quickly understand the quality of applications after release. Adapt to the latest iOS, Android official operating system, goose factory engineers are using, come to join us quickly…