Android event bus framework design: eventbus3.0 source details and architecture analysis (2)

Time:2020-4-4

Blog Homepage

1. Viscous event

Sticky events can be received by subscribing to the event after sending it, similar to sticky broadcast.

First, let’s look at the release mode of sticky events:

EventBus.getDefault().postSticky("hello, eventbus!");

Let’s look at the source code of the sticky event method

private final Map<Class<?>, Object> stickyEvents;

    public void postSticky(Object event) {
        synchronized (stickyEvents) {
            //Cache events in stickyevents first
            //Stickyevents is a map, key is the event type, and value is the event
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        //Then publish the event. If it is not registered, the subscription event method will not be executed
        post(event);
    }

The poststicky method mainly does two things. First, it caches the events in stickyevents, and then publishes the events through the post method. This method has been analyzed before, and is not analyzed here.

After publishing the sticky event, when subscribing to the sticky event method, it will execute immediately after registration. The core registration process is the previously analyzed register method, in which a piece of code of the subscribe method deals with sticky events

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        // ......

        //If sticky is set to true in @ subscribe annotation when subscribing event method, it represents sticky event
        if (subscriberMethod.sticky) {
            //Eventinheritance defaults to true, which means that inheritance events will also be executed
            if (eventInheritance) {
                // Existing sticky events of all subclasses of eventType have to be considered.
                // Note: Iterating over all events may be inefficient with lots of sticky events,
                // thus data structure should be changed to allow a more efficient lookup
                // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
                Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
                for (Map.Entry<Class<?>, Object> entry : entries) {
                    Class<?> candidateEventType = entry.getKey();
                    if (eventType.isAssignableFrom(candidateEventType)) {
                        Object stickyEvent = entry.getValue();
                        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                    }
                }
            } else {
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    }

It can be seen that when dealing with sticky events, we traverse stickyevents during registration, and then give it to checkpoststickyeventtosubscription for processing

    private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if (stickyEvent != null) {
            // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
            // --> Strange corner case, which we don't take care of here.
            postToSubscription(newSubscription, stickyEvent, isMainThread());
        }
    }

The checkpoststickyeventtosubscription method finally calls the thread switching method posttosubscription, which completes the event processing.

2.Subscriber Index

When analyzing eventbus registration, the default implementation is to find all subscription events in the registration class through reflection technology. If there are many subscription event methods in the current registration class, the runtime performance will be affected through reflection technology. Therefore, in eventbus3.0, through apt (annotation processor) technology, all subscription event methods are found in the compiler, and a supplementary index, subscriber [index], is generated to save all subscription methods.

To find the method information of subscription events during project compilation, first add the following configuration to build.gradle of app:

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                //Parameters set by apt, specifying the secondary index class name and package name
                arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
            }
        }
    }
}

dependencies {
    implementation 'org.greenrobot:eventbus:3.1.1'
    //Introduction of annotation processor
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}

Then add the following configuration in the application of the project to generate a default eventbus instance:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

EventBus eventBus = EventBus.getDefault();

After compilation, myeventbusindex class will be generated. The source code is as follows:

public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(com.example.eventbus.demo.MainActivity.class, true,
                new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onMessageEventMain", com.example.eventbus.demo.MessageEvent.class,
                    ThreadMode.MAIN, 0, true),
        }));

        putIndex(new SimpleSubscriberInfo(com.example.eventbus.demo.TestActivity.class, true,
                new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onMessageEventMainForTest", com.example.eventbus.demo.MessageEvent.class,
                    ThreadMode.MAIN, 0, true),
        }));

    }

    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;
        }
    }
}

Among them, the subscriber [index] is a map, which holds the class and subscription event method information of the current registered class.

Let’s start with myeventbusindex

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();

Invoke the builder method of EventBus to create a EventBusBuilder object, then call its addIndex method and add the index class.

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

As you can see from the above code, add the generated index class to the subscriberInfoIndexes collection, then call the installDefaultEventBus method to create the default EventBus instance.

public EventBus installDefaultEventBus() {
        synchronized (EventBus.class) {
            if (EventBus.defaultInstance != null) {
                throw new EventBusException("Default instance already exists." +
                        " It may be only set once before it's used the first time to ensure consistent behavior.");
            }
            EventBus.defaultInstance = build();
            return EventBus.defaultInstance;
        }
    }

    public EventBus build() {
        //This represents the instance of eventbusbuilder, so that eventbus can get the subscriberinfoindexes collection
        return new EventBus(this);
    }

Where the subscriberinfoindexes collection is passed to the eventbus through the eventbus structure

    EventBus(EventBusBuilder builder) {
        // ...
        indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        // ...
    }

When analyzing the registration process, the findusinginfo method will handle this index class. Let’s look at the source code

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        // FindState
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            //If the apt (annotation processor) is not used to generate the subscription method index and null is returned, it will enter the else statement
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                        findState.subscriberMethods.add(subscriberMethod);
                    }
                }
            } else {
                //Use reflection technology to find all subscription methods in the current registered class
                findUsingReflectionInSingleClass(findState);
            }
            //Continue searching from the parent until the parent is null
            findState.moveToSuperclass();
        }
        //Return all subscription methods in the registration class, release the state in findstate, and put the findstate object back into the cache pool
        return getMethodsAndRelease(findState);
    }

When we use apt to generate and use the index class, we will not use reflection technology to find all the subscription event methods in the registered class.

private SubscriberInfo getSubscriberInfo(FindState findState) {
        if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
            SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
            if (findState.clazz == superclassInfo.getSubscriberClass()) {
                return superclassInfo;
            }
        }
        //Subscriberinfoindexes is to add a collection of index classes through the addindex method
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

The subscriberinfoindexes collection is created through the addindex method, and the index class instance myeventbusindex is saved.

By using subscriber index, we can avoid reflection processing and improve performance.

3.AsyncExecutor

Asynexecutor is similar to a thread pool, but can handle running exceptions.

    public static AsyncExecutor create() {
        return new Builder().build();
    }

Through the AsyncExecutor static method, create can create a AsyncExecutor instance object and then call its execute method to perform the task.

    public void execute(final RunnableEx runnable) {
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    runnable.run();
                } catch (Exception e) {
                    Object event;
                    try {
                        event = failureEventConstructor.newInstance(e);
                    } catch (Exception e1) {
                        eventBus.getLogger().log(Level.SEVERE, "Original exception:", e);
                        throw new RuntimeException("Could not create failure event", e1);
                    }
                    if (event instanceof HasExecutionScope) {
                        ((HasExecutionScope) event).setExecutionScope(scope);
                    }
                    eventBus.post(event);
                }
            }
        });
    }

When an exception occurs, the throwablefailureevent event will be published, and we can subscribe to the event for corresponding processing. The parameter received by the execute method is the runnableex object

    public interface RunnableEx {
        void run() throws Exception;
    }

The run method in runnableex can throw exceptions.

We can modify the default thread pool ThreadPool according to another static method builder of asynexecutor, the failed event type failureeventtype

//Asynexecutor.java source code
    public static Builder builder() {
        return new Builder();
    }

   public static class Builder {
        private Executor threadPool;
        private Class<?> failureEventType;

        public Builder threadPool(Executor threadPool) {
            this.threadPool = threadPool;
            return this;
        }

        public Builder failureEventType(Class<?> failureEventType) {
            this.failureEventType = failureEventType;
            return this;
        }
   }

All the source codes of eventbus have been analyzed

If my article is helpful to you, please give me a compliment

Recommended Today

PHP Basics – String Array Operations

In our daily work, we often need to deal with some strings or arrays. Today, we have time to sort them out String operation <?php //String truncation $str = ‘Hello World!’ Substr ($STR, 0,5); // return ‘hello’ //Chinese string truncation $STR = ‘Hello, Shenzhen’; $result = mb_ Substr ($STR, 0,2); // Hello //First occurrence of […]