Learning Summary of Eventbus Source Code

Posted by shadow-x on Wed, 15 May 2019 17:15:18 +0200

Reprinted please indicate the source
https://blog.csdn.net/dreamsever/article/details/80031988

There are many source code parsing about Eventbus on the internet, but I still have to write it down by myself and see others'source code parsing. Maybe it can make me understand the general idea, but using my own language to summarize and analyze will make my record deeper and experience deeper.

Maybe look at the map given to us by EventBus officials, so that we can understand it better. We can translate it into Event Bus or Event Bus. The first publisher is where we execute EventBus.getDefault().post(new MyEvent()). new MyEvent() is an event, and then through the EventBus bus or bus, we can pass the event as a bus or pipeline to the place where we subscribe to the event. EventBus is both a publisher, but at the same time, EventBus is somewhat similar to the role of a newspaper delivery brother, who subscribes to newspapers and delivers them today's new newspapers.

Then take a look at the construction process of EventBus as a multi-role character

public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

The multithreaded secure singleton pattern creates EventBus objects and continues to look at the construction method

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
public EventBus() {
    this(DEFAULT_BUILDER);
}

Use Builder to create a new EventBus, Builder mode is familiar to everyone, notifications, dialog boxes, and many other places use this form. But here is a little different, the default Builder is to give us some properties by default, go get them directly. Look at the following construction method, I believe that the first feeling you see is that there are many new sets, generics, unknown sets... I feel I have no confidence to look down. I'd like to say don't look at these collections first, skip them and look down.

//
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
private final Map<Class<?>, Object> stickyEvents;

EventBus(EventBusBuilder builder) {
    //
    subscriptionsByEventType = new HashMap<>();
    typesBySubscriber = new HashMap<>();
    stickyEvents = new ConcurrentHashMap<>();
    mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    eventInheritance = builder.eventInheritance;
    executorService = builder.executorService;
}

Look here first. What do you want to see first? First look at the registration method. We all know that the use of Eventbus is to register the onCreate()/onStart() method in Activity and then cancel the registration in the onDestroy()/onStop() method. Then write a subscription method, similar to the following:

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(GlobalEvent event) {
    //do some thing
}

ok, let's first look at the registration method parameters.

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        //Traverse through all subscription methods to execute the subscription
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

Obviously, our subscription objects, such as Activity or Fragment, are accepted with Object objects, which means that all objects can be accepted. FindSubscriber Methods here is the subscription method to get this subscription object. As for how to implement it, we should not look at it first, but continue to look at subscribe (subscriber, subscriber method).

//The incoming parameters are the subscription object and the subscription method
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //What exactly does the event type of the subscription method refer to? In fact, the GlobalEvent above us is an event type.
    Class<?> eventType = subscriberMethod.eventType;
    //A subscriber and a subscription method constitute a subscription transaction.
    //This subscription transaction is a combination of subscribers, subscription methods, and subscribed events.
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //What do you see next? Subscriptions ByEventType, as if it had,
    //Here is a collection of subscription transactions based on the type of event, so we seem to understand.
    //What's the ghost of subscriptions ByEventType? key is the type of subscription method.
    //value is a collection of subscription transactions for all of the subscription method types
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        //New subscription transaction set is empty
        subscriptions = new CopyOnWriteArrayList<>();
        //Add subscriptions ByEventType
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            //Adding Subscription Transactions into Subscription Transactions Collection Based on Event Priority
            subscriptions.add(i, newSubscription);
            break;
        }
    }
    //What do you see here? typesBySubscriber seems to have a key as a subscription object.
    //value is an event type, which is a collection of subscription time types bound to each subscriber stored.
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);
    //Avoid going into solitary forces. The sticky incident is not considered here.
    if (subscriberMethod.sticky) {
        . . . 
    }
}

Now we should all know what the above sets are. Let's review:
// key is event type and value is HashMap for all subscription transactions of that subscription event type
subscriptionsByEventType = new HashMap<>();
// key is the subscription object and value is the HashMap of event type
typesBySubscriber = new HashMap<>();

Then take a look at the unregister method

public synchronized void unregister(Object subscriber) {
    //Get all the event types
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            //Unsubscription Event
            unsubscribeByEventType(subscriber, eventType);
        }
        //Finally, remove the key-value pair where subscriber is the key in typesBySubscriber
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

//
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    //Get all subscribers who subscribe to this event type first, and there must be subscribers in it.
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            //If you find subscriber here, remove it.
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

At this point, EventBus's registration and cancellation are over. Now we need to publish an event to see how it actually publishes and delivers this event.
EventBus.getDefault().post(new MyEvent());

// This paper defines a Posting ThreadState in the ThreadLoacal way. If you don't know about ThreadLoacal, you can learn it first. It is used to guarantee the uniqueness of variables in each thread, and it is also a form of thread safety. This is to ensure that the variable PostingThreadState in the current thread is independent of other threads in this thread, and other threads will not modify it, similar to Handler mechanism, to ensure that EvetQueue in PostingThreadState will not be operated by other threads.

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};

Release events

public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    //Adding Events to Event Queue
    eventQueue.add(event);

    if (!postingState.isPosting) {
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            while (!eventQueue.isEmpty()) {
                //Publish a single event
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    //Get Event's class
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    //EvetInheritance defaults to true, and event inheritance is supported by default
    if (eventInheritance) {
        //Find all eventClass es, including parent and child classes
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            //Traverse to execute the postSingleEventForEventType method with a return value
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        //Get all the subscription transactions for this event
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    //Do not find the corresponding transaction for null representation, return true, otherwise return false assignment to subscriptionFound 
    if (subscriptions != null && !subscriptions.isEmpty()) {
        //Traverse through all subscription transactions
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                //To deal with this matter
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

Here we seem to have seen the result, that is, according to the threadMode distribution to perform different methods to deal with, invokeSubscriber is executed with our commonly used MAIN as an example.

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
            invokeSubscriber(subscription, event);
            break;
        case MAIN:
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case BACKGROUND:
            if (isMainThread) {
                backgroundPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC:
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}

void invokeSubscriber(Subscription subscription, Object event) {
    try {
        //Using reflection, execution method
        subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
    } catch (InvocationTargetException e) {
        handleSubscriberException(subscription, event, e.getCause());
    } catch (IllegalAccessException e) {
        throw new IllegalStateException("Unexpected exception", e);
    }
}

At this point, Eventbus's basic process of registration anti-registration, publishing to execution events, but not yet finished. An interview was asked, EventBus uses compile-time annotations to improve efficiency, you know? I am very confused to say that I do not know, see now, ask you the same question, you should not know where it is, then we go to find compile-time annotations.

Please look here. http://greenrobot.org/eventbus/documentation/subscriber-index/
Originally, we have not used this new function from beginning to end. Let's see how this new function is used. The above official website is very detailed. If you are interested, you can see it.

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
            }
        }
    }
}

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

See here EventBus automatically generates MyEventBusIndex files for us

Initialize EventBus

EventBus mEventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
addIndex method

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

Then the subscriberMethodFinder's construction method is used when building. Why is subscriberMethodFinder used? Let's go on and see.

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

MyEventBusIndex() is the SubscriberInfoIndex that we compiled and generated, which is called index here. Let's take a look at what's going on inside, and we'll use it later. SUBSCRIBER_INDEX is a HashMap, key is a subscriber, value is SubscriberInfo. Here we use SimpleSubscriberInfo, a subclass of SubscriberInfo, which contains
private final Class subscriberClass;
private final SubscriberMethodInfo[] methodInfos;
Subscribers, subscription method arrays, and so on

/** This class is generated by EventBus, do not edit. */
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.test.first.fragment.SticyNavFragment.class, true,
                new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventMainThread",
                    com.example.test.first.eventbus.StickyNavRefreshEvent.class, ThreadMode.MAIN),
            new SubscriberMethodInfo("onMessageEvent",
                    com.example.test.first.eventbus.StickyNavRefreshEvent.class, ThreadMode.MAIN),
        }));

        putIndex(new SimpleSubscriberInfo(com.example.test.first.fragment.TestEventBusStubFragment.class, true,
                new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onMessageEvent",
                    com.example.test.first.eventbus.StickyNavRefreshEvent.class, ThreadMode.MAIN),
            new SubscriberMethodInfo("onMyOwnEvent", com.example.test.first.eventbus.StubEvent.class,
                    ThreadMode.MAIN),
        }));

        putIndex(new SimpleSubscriberInfo(com.example.test.first.fragment.PartThreeFragment.class, true,
                new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onEventMainThread",
                    com.example.test.first.eventbus.AppBarOffsetEvent.class, ThreadMode.MAIN),
        }));

    }

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

Okay, so we've also generated our indexes here. Where do we use these indexes? The answer is at the time of registration.
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); this findSubscriberMethods method we haven't looked at, it's time to look at it.

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //Go to the cache first.
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    //Regardless of whether the index is generated or not
    if (ignoreGeneratedIndex) {
        //Whether there is an index or not, use reflection instead of index.
        //User specific requirements set, default here is false
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        //ignoreGeneratedIndex defaults to false, so I'm here to focus on the findUsingInfo method
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

The findUsingInfo method encapsulates the process of obtaining a set of subscription methods

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    //Return to a non-empty FindState, where the variable pool FIND_STATE_POOL is also used to improve performance
    FindState findState = prepareFindState();
    //Initialize clazz, etc.
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        findState.subscriberInfo = getSubscriberInfo(findState);

        if (findState.subscriberInfo != null) {
            //Here we get the method array based on the index
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            //Traversal method array
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            //Reflections that are not found in the index and are still in use
            findUsingReflectionInSingleClass(findState);
        }
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}

// Double checking mechanism to check whether this method should be added
There are three situations to consider here:
First: the subclass does not override the subscription method in the parent class and does not have the same Event event in the subclass
Second: The subclass does not copy the subscription method in the parent class, but the subclass contains the subscription method for subscribing to the same Event, that is, there are two ways to accept it for the same event. This situation is not recommended, but you have to use or prevent you, it can also be used.
Third, subclasses override parent methods

In the first case, a subclass subscription method is added first, and each time (existing == null) returns true directly.
The second situation: an existing is not null, because the same event happened twice, it must belong to Method. First, we made a check AddWithMethod Signature method to judge, look down.
boolean checkAdd(Method method, Class

private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
    methodKeyBuilder.setLength(0);
    methodKeyBuilder.append(method.getName());
    methodKeyBuilder.append('>').append(eventType.getName());

    String methodKey = methodKeyBuilder.toString();
    Class<?> methodClass = method.getDeclaringClass();
    Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
    if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
        // Only add if not already found in a sub class
        return true;
    } else {
        // Revert the put, old class is further down the class hierarchy
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false;
    }
}

Now all the methods have been added to getMethods AndRelease (FindState); this is actually a return to List subscriberMethods, and other operations have been done to assign FIND_STATE_POOL variable pool, variable pool can prevent a large number of variables from being created and occupy memory.

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
    List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
    findState.recycle();
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            if (FIND_STATE_POOL[i] == null) {
                FIND_STATE_POOL[i] = findState;
                break;
            }
        }
    }
    return subscriberMethods;
}

Here we will


List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);

This code has been analyzed. It's still a bit difficult to analyze. If you don't understand it very well, it's suggested that you write the code and see if the breakpoint works like analysis. This may help you understand it more deeply and more easily.

Conclusion:

EventBus source code is still worth studying and learning, can be installed 100 million + libraries certainly have its advantages. Users are very convenient to use, the amount of code is not much, and the performance is good. There are many things we can learn, such as reflection, compile-time annotations, variable pools, thread-safe processing. There are also many small details to take an Object existing = anyMethodByEventType.put(eventType, method); get a replacement object at the same time as put, which is very clever to use, methodKeyBuilder.setLength(0); reuse StringBuilder and so on.

Reference link

https://www.cnblogs.com/bugly/p/5475034.html
https://juejin.im/entry/58710e9eb123db4a2eb866c4

Topics: Fragment Android