Android EventBus usage understanding

Advantages of EventBus

  1. Simplify the interaction between components.
    Decouple event senders and receivers
    from Activities, Fragments, and background threads. Use well
    to avoid complex and error-prone libraries and life cycle problems
  . 2. Make your code more concise.
  3. It is fast
  4. The file is extremely small (jar package is about 60k)
  5. It has advanced features such as delivery thread, subscriber priority, etc.
  6. In actual use, the APP has been installed more than 1 billion times.
  These features are officially written by them. We Now use an example to see its use.

Example of use

  Here we use an Activity and a Fragment as an example. Let's take a brief look at how EventBus is used.
  Here is the code for MainActivity:

public class MainActivity extends FragmentActivity {
    
    
    public static final String TAG = "MainActivity";
    private TextView main_tv;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_ac);
        main_tv = findViewById(R.id.main_tv);
        FragmentManager fmanager = getSupportFragmentManager();
        FragmentTransaction trac = fmanager.beginTransaction();
        trac.add(R.id.main_fl, new BusFragment());
        trac.commit();
        EventBus.getDefault().register(this);
    }
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(MessageEvent met) {
    
    
        main_tv.setText(met.toString());
        Log.e(TAG, "onMessageEvent接收到消息:" + met);
    }

    @Override
    protected void onDestroy() {
    
    
        EventBus.getDefault().unregister(this);
        super.onDestroy();
    }
}

  There is mainly a text control TextView main_tv, and a BusFragment is added.
  It executes the registered subscriber in onCreate(), where the subscriber is MainActivity. Perform deregistration in onDestroy().
  At the same time, it also uses the @Subscribe annotation to declare receiving methods and events.
  Events are defined by classes, here is MessageEvent

public class MessageEvent {
    
    
    int count;
    String con;
    @Override
    public String toString() {
    
    
        return "MessageEvent{" +
                "count=" + count +
                ", con='" + con + '\'' +
                '}';
    }
}

  There are two variables count and con in the event class.
  Look at the code in BusFragment again:

public class BusFragment extends Fragment {
    
    
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
    
    
        View view = inflater.inflate(R.layout.bus_frag, container, false);
        Button frag_btn = view.findViewById(R.id.frag_btn);
        frag_btn.setOnClickListener(itview ->{
    
    
            MessageEvent et = new MessageEvent();
            et.count = 10;
            et.con = "测试内容";
            EventBus.getDefault().post(et);
        });
        return view;
    }
}

  There is a button frag_btn in BusFragment. After clicking, the event class will be assigned, and then EventBus.getDefault().post(et) will be called to publish the event.
  In this way, events can be published to MainActivity, and MainActivity can receive event messages in its onMessageEvent().
  In this way, the interaction between Activity and Fragment can be realized. Judging from the code, it is relatively simple.

source code analysis

  The following is an analysis from the three aspects of registering subscribers, publishing events, and unregistering subscribers.

Register Subscriber

  The call is EventBus.getDefault().register(this), where this is MainActivity, which is the subscriber.
  First call EventBus.getDefault() to generate a default EventBus object, and then call the registered subscriber register(this).

Generate a default EventBus object

  The member variables of EventBus, pick a few important ones, as follows:
EventBus member variables

EventBus member variables
  subscriptionsByEventType: It is a container, its key is the Class of the event class, and its value is a collection of Subscriptions. The Subscription class is a description of information about subscribers and subscription methods. Subscription has two member variables Object subscriber and SubscriberMethod subscriberMethod. Among them, subscriber is the subscriber, and subscriberMethod is the information of the subscription method. typesBySubscriber: The key is the subscriber, and the value is the event class Class that the subscriber subscribes to.

  stickyEvents: It is related to sticky events, the key is the Class of the sticky event class, and the value is the sticky event class object.
  mainThreadSupport: Used to determine whether the currently executing thread is in the main thread.
  mainThreadPoster: Mainly used to handle the execution of subscription methods whose threadMode is MAIN or MAIN_ORDERED, and put them in the main thread for execution.
  backgroundPoster: It is mainly used to process the execution of the subscription method whose threadMode is Background. When the subscription method is to be executed, if it is found to be in the main thread, backgroundPoster will execute the subscription method in the current background thread.
  asyncPoster: It is mainly used to handle the execution of the subscription method whose threadMode is ASYNC. The main emphasis here is asynchronous, that is, when the subscription method is to be executed, a new thread must be started to execute the method.
  subscriberMethodFinder: As the name suggests, the finder of the subscription method. That's right, the subscription method found in this class. Details will be given later.
  executorService: thread pool, which defaults to Executors.newCachedThreadPool().
  throwSubscriberException: defaults to false. If an exception occurs when executing the subscription method, whether an exception will be thrown.
  sendSubscriberExceptionEvent: The default is true. If an exception occurs when executing the subscription method, whether to send the SubscriberExceptionEvent event.
  sendNoSubscriberEvent: The default is true. When publishing an event, if no corresponding subscription method is found, whether to publish the NoSubscriberEvent event.
  eventInheritance: When publishing an event, it will not only publish its own type, but also publish all interfaces and class event types it inherits. The default is true; if you subscribe to sticky events, you will find events of itself and its subclasses and publish them.
  The construction of EventBus uses the builder pattern, using EventBusBuilder. Take a look at EventBus.getDefault():

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

  A typical singleton generates double checks, because the defaultInstance is null at first, so it will go to the initialization function of EventBus

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

    EventBus(EventBusBuilder builder) {
    
    
        logger = builder.getLogger();
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadSupport = builder.getMainThreadSupport();
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        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;
    }

  DEFAULT_BUILDER is a static variable of the EventBus class. As we can see, the classic build() method is not used here, but an assignment method is used to generate the EventBus object. Each of the variables has been mentioned above when introducing the member variables of the class object.

Register Subscriber

  Take a look at the register(Object subscriber) of EventBusmethod:

    public void register(Object subscriber) {
    
    
        if (AndroidDependenciesDetector.isAndroidSDKAvailable() && !AndroidDependenciesDetector.areAndroidComponentsAvailable()) {
    
    
            // Crash if the user (developer) has not imported the Android compatibility library.
            throw new RuntimeException("It looks like you are using EventBus on Android, " +
                    "make sure to add the \"eventbus\" Android library to your dependencies.");
        }

        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
    
    
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
    
    
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

  The parameter subscriber is the subscriber, in the above example, the MainActivity instance.
  First get the subscriber's Class, and then call the findSubscriberMethods() of the EventBus member variable subscriberMethodFinder to get the subscription method on the corresponding Class (the method marked with @Subscribe).
  Call the subscribe(subscriber, subscriberMethod) method in a loop to subscribe the subscriber to the subscriberMethod. In fact, put subscriber and subscriberMethod into the corresponding data structure (mainly subscriptionsByEventType and typesBySubscriber), and when waiting for the subscription event to be published, go to the relevant data structure to get relevant information and execute the subscription method.

Get the subscription method implemented by the subscriber

  It is mainly obtained by calling subscriberMethodFinder.findSubscriberMethods(subscriberClass). subscriberMethodFinder is a SubscriberMethodFinder class object, and the parameter subscriberClass is the Class of the subscriber obtained above. Its code execution method is as follows:
findSubscriberMethods execution flow

findSubscriberMethods() code execution flow

  Now look at the findSubscriberMethods() method of the specific SubscriberMethodFinder class:

    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    
    
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
    
    
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
    
    
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
    
    
            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 parameter subscriberClass is the Class of the subscriber object. Multiple subscription methods are allowed on the subscriber object, so the result of the search is a collection List. METHOD_CACHE is a ConcurrentHashMap<Class<?>, List> object, which is used as a cache. If it has been searched before, the result will be stored, and the key value is the Class of the subscriber object. So if the corresponding result is not null after being queried from the cache, it will be returned directly.
  ignoreGeneratedIndex is false by default, so call findUsingInfo(subscriberClass) to get the result.
  Finally, the result will be put into METHOD_CACHE.
  Before we look at findUsingInfo(subscriberClass), we need to understand the FindState class structure as follows:
FindState class structure diagram

FindState class structure diagram
  subscriberMethods: It is all the subscription methods found out.

  anyMethodByEventType: It is used for judgment during the search process, the key is the Class of the event type, and the value is the Method or FindState object.
  subscriberClassByMethodKey is also used to make judgments during the search process. The key is the method name + ">" + event type name, and the value is the Class of the life class of the method (the Class of the subscription class).
  methodKeyBuilder: is used to spell the key value of subscriberClassByMethodKey
  subscriberClass: the Class of the subscriber class
  clazz: the Class of the subscriber class at the beginning, and will recurse to its parent class later
  skipSuperClasses: whether to skip the search for methods in the class of the parent class
  subscriberInfo : Subscriber information, which is currently null.
  The FindState class also uses a cache mechanism. Its implementation is SubscriberMethodFinder's FIND_STATE_POOL. It is a FindState array with size 4.
  Now you can continue to look at findUsingInfo()way.

    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    
    
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
    
    
            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 {
    
    
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

  The prepareFindState() method is to first take the object in FIND_STATE_POOL, if not, it will regenerate one.
  findState.initForSubscriber(subscriberClass) is to set the subscriberClass and clazz of findState as the class of the subscriber. skipSuperClasses is false and subscriberInfo is null.
  The following will enter a while loop, because findState.subscriberInfo is null, so the current findState.clazz is not null. After findUsingReflectionInSingleClass() is finished, it will traverse to its parent class and continue to execute. Turning to the superclass Class is achieved through findState.moveToSuperclass().
  Finally, getMethodsAndRelease(findState) will be called to get the result stored in the subscriberMethods member of the findState object, and put findState into the cache pool FIND_STATE_POOL.
  The main processing here is findUsingReflectionInSingleClass(findState). After reading it, we can know how EventBus implements the search subscription method through reflection.

    private void findUsingReflectionInSingleClass(FindState findState) {
    
    
        Method[] methods;
        try {
    
    
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
    
    
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            try {
    
    
                methods = findState.clazz.getMethods();
            } catch (LinkageError error) {
    
     // super class of NoClassDefFoundError to be a bit more broad...
                String msg = "Could not inspect methods of " + findState.clazz.getName();
                if (ignoreGeneratedIndex) {
    
    
                    msg += ". Please consider using EventBus annotation processor to avoid reflection.";
                } else {
    
    
                    msg += ". Please make this class visible to EventBus annotation processor to avoid reflection.";
                }
                throw new EventBusException(msg, error);
            }
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
    
    
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
    
    
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
    
    
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
    
    
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
    
    
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
    
    
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + parameterTypes.length);
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
    
    
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

  Currently findState.clazz is the class of the subscriber, which corresponds to the class of MainActivity in the above example. Through the reflection mechanism, call its getDeclaredMethods() method to get all the methods declared by its own class. If an exception occurs in this method, getMethods() will be called. Note that getMethods() will return the included public methods inherited from superclasses and superinterfaces, and will set skipSuperClasses to true. Because all the methods have been obtained, there is no need to traverse to the parent class.
  Then loop through the queried Method.
  First determine the modifier of the method, it can be seen that the subscription method needs to be set to public. And you can't bring the following four modifiers, Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC. Modifier.ABSTRACT | Modifier.STATIC We know these two, abstract method or static method. BRIDGE | SYNTHETIC is a concrete type inherited from a generic class, and the inherited generic class contains a generic method, then the method will be marked with BRIDGE | SYNTHETIC. Here we need to know that the corresponding method on this class declares that the subscription method does not work.
  The parameter of the subscription method can only be one, and the Subscribe annotation is required. strictMethodVerification is false by default. If it is set to true, an exception EventBusException will be reported.
  eventType is the Class of the method parameter, that is, the Class of the subscribed event class. It will judge whether the method and related information can be added to findState.subscriberMethods through findState.checkAdd(method, eventType). The SubscriberMethod class is to describe the subscription method. Its members include the execution method Method, the event type, and the threadMode, priority, and sticky obtained by annotating Subscribe.
  Let's take a look at findState.checkAdd(method, eventType) to see under what circumstances a method can be added.

        boolean checkAdd(Method method, Class<?> eventType) {
    
    
            // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
            // Usually a subscriber doesn't have methods listening to the same event type.
            Object existing = anyMethodByEventType.put(eventType, method);
            if (existing == null) {
    
    
                return true;
            } else {
    
    
                if (existing instanceof Method) {
    
    
                    if (!checkAddWithMethodSignature((Method) existing, eventType)) {
    
    
                        // Paranoia check
                        throw new IllegalStateException();
                    }
                    // Put any non-Method object to "consume" the existing Method
                    anyMethodByEventType.put(eventType, this);
                }
                return checkAddWithMethodSignature(method, eventType);
            }
        }

  It can be seen that every time a new event type is added, it must be added. Because the value corresponding to the key in anyMethodByEventType is null.
  What if the same type of event is added for the second time? There are two cases, that is, whether the Method method name is the same as the one added before. It is also found that when adding the same event type for the second time, the checkAddWithMethodSignature() method will be executed twice. Discuss in two cases
  1. The method name is different from before.
  The previously added Method will be obtained through anyMethodByEventType.put(eventType, method). Because it is a Method, it will enter the first checkAddWithMethodSignature() method to judge.

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

  See it, the key value will be fangfaming + ">" + event class name as the key value. Get the Class of the declared method, add it to subscriberClassByMethodKey, add this key value for the first time, so the obtained methodClassOld is null, and return true directly. When you want to be clear here, method is the method when you add the event for the first time.
The first execution of checkAddWithMethodSignature() returns true, after which, the value in anyMethodByEventType will be updated to the FindState class object. Then go a second time. This time, method is the method to be added. Go to the checkAddWithMethodSignature() method again. Because the method name is different, the key value is generated. There is still no corresponding value in subscriberClassByMethodKey. After adding, methodClassOld is still null, so return true.
  At this time, it is possible to add the subscription method to the result.
  2. The method name is the same as before.
  The difference between this time and the first case is that when the checkAddWithMethodSignature() is executed in the second pass, the situation in the first pass is the same.
  The second checkAddWithMethodSignature generates the value of the key. Since the method name and event name are the same, when subscriberClassByMethodKey.put(), the returned result is not null. It is the declaring class of the method when the method is first added. Then make methodClassOld.isAssignableFrom(methodClass) judgment, methodClassOld is the declaration class of the method when the method is added for the first time. methodClass is the declaration class of the method added for the second time.
  methodClassOld.isAssignableFrom(methodClass) returns true when methodClassOld is judged to be the parent class, interface class or equal to methodClass. That is, when the class of this type of event added for the first time is the parent class, interface class or equal to the class of the second added type, true will be returned.
  Let's think again, under what circumstances, a subscription method with the same method name and event type will be added. Certainly not in the same class, there are only functions with the same method name and the same event defined in both the subclass and the parent class. Through findUsingInfo() , we know that the event is added from the subclass first, so the class that adds this type of event for the first time is a subclass of the class that adds the second type. When traversing to the parent class add method, it must pass no. So we know that the member subscriberClassByMethodKey of the FindState class is mainly to prevent this situation.

  In this way, return to the register(Object subscriber) method subscriberMethodFinder.findSubscriberMethods(subscriberClass) is executed, and the search results are all in subscriberMethods.

Subscriber subscription method

  Take a look at the corresponding subscribe() method code. The code is a bit long, so let’s look at it in sections:

    // Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    
    
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
    
    
            subscriptions = new CopyOnWriteArrayList<>();
            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) {
    
    
                subscriptions.add(i, newSubscription);
                break;
            }
        }


  It mainly encapsulates subscribers and subscription objects into Subscription, and then puts it into subscriptionsByEventType.
  Mr. Generate Subscription object newSubscription, which contains subscriber subscriber and subscription method subscriberMethod. subscriptionsByEventType uses the type of subscription event as the key. If it is empty, a new one will be created and put into subscriptionsByEventType.
  Then the location will be found according to the size of the priority. The higher the priority, the higher the position.
  Look again at the second piece of code in the subscribe() method:

        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
    
    
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

  This code is quite simple, put the event type subscribed by the subscriber into typesBySubscriber.
  Let's look at the last piece of code of the subscribe() method

        if (subscriberMethod.sticky) {
    
    
            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);
            }
        }
    }

  If the current subscription method is sticky, the events in stickyEvents will be checked, and if they are subclasses or similar to the event type of the current subscription method, the events will be published. This will be realized. After the sticky event is published, and then subscribed, the corresponding event can also be received.
  The eventInheritance was explained earlier, as well as the isAssignableFrom() method. Here is to traverse the events in stickyEvents. If the event type is the same as the currently bound eventType, or its subclass, checkPostStickyEventToSubscription(newSubscription, stickyEvent) will be called to execute the release. Take a look at it:

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

  If the sticky event is not null, execute postToSubscription(newSubscription, stickyEvent, isMainThread()) to execute the release. The content of the release is described below.

post event

  Posting an event is calling EventBus.getDefault().post(Object event):

    /** Posts the given event to the event bus. */
    public void post(Object event) {
    
    
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {
    
    
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
    
    
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
    
    
                while (!eventQueue.isEmpty()) {
    
    
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
    
    
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

  currentPostingThreadState is related to threads, calling get() will get a PostingThreadState class. Its member eventQueue is of type ArrayList and is used as an event queue. Here the event is put on the queue.
  postingState.isPosting represents whether the event is being sent. postingState.isPosting is false, the message will be sent below, and then postingState.isPosting will be set to true. postingState.isMainThread is assigned isMainThread(), indicating whether the current thread is the main thread.
  Then start a loop to call postSingleEvent() to send the events in the queue. Finally, set postingState.isPosting = false, postingState.isMainThread = false.

Is it in the main thread

  Take a look at isMainThread() to see how it determines the main thread

    private boolean isMainThread() {
    
    
        return mainThreadSupport == null || mainThreadSupport.isMainThread();
    }

  If mainThreadSupport is null or mainThreadSupport.isMainThread(). Here mainThreadSupport is defined in EventBusBuilder, take a look

    MainThreadSupport getMainThreadSupport() {
    
    
        if (mainThreadSupport != null) {
    
    
            return mainThreadSupport;
        } else if (AndroidComponents.areAvailable()) {
    
    
            return AndroidComponents.get().defaultMainThreadSupport;
        } else {
    
    
            return null;
        }
    }

  It can be seen that it is mainly obtained through AndroidComponents.get().defaultMainThreadSupport, and AndroidComponents.get() is:

    private static final AndroidComponents implementation;

    static {
    
    
        implementation = AndroidDependenciesDetector.isAndroidSDKAvailable()
            ? AndroidDependenciesDetector.instantiateAndroidComponents()
            : null;
    }

    public static boolean areAvailable() {
    
    
        return implementation != null;
    }

    public static AndroidComponents get() {
    
    
        return implementation;
    }
    public final Logger logger;
    public final MainThreadSupport defaultMainThreadSupport;

    public AndroidComponents(Logger logger, MainThreadSupport defaultMainThreadSupport) {
    
    
        this.logger = logger;
        this.defaultMainThreadSupport = defaultMainThreadSupport;
    }    

  You can see that implementation is obtained by AndroidDependenciesDetector.instantiateAndroidComponents():

    private static final String ANDROID_COMPONENTS_IMPLEMENTATION_CLASS_NAME = "org.greenrobot.eventbus.android.AndroidComponentsImpl";    
    public static AndroidComponents instantiateAndroidComponents() {
    
    

        try {
    
    
            Class<?> impl = Class.forName(ANDROID_COMPONENTS_IMPLEMENTATION_CLASS_NAME);
            return (AndroidComponents) impl.getConstructor().newInstance();
        }
        catch (Throwable ex) {
    
    
            return null;
        }
    }

  It can be seen that the real initialization is the AndroidComponentsImpl class

public class AndroidComponentsImpl extends AndroidComponents {
    
    

    public AndroidComponentsImpl() {
    
    
        super(new AndroidLogger("EventBus"), new DefaultAndroidMainThreadSupport());
    }
}

  You can see that its defaultMainThreadSupport is the DefaultAndroidMainThreadSupport class object.
  So the mainThreadSupport of the EventBus object is the DefaultAndroidMainThreadSupport class object.
  Take a look at its isMainThread()

    @Override
    public boolean isMainThread() {
    
    
        return Looper.getMainLooper() == Looper.myLooper();
    }

  Determine whether the Looper of the current thread is equal to the Looper.getMainLooper() of the main thread. If it is equal, it is in the main thread. If not, it is no longer the main thread.

publish a single event

  Take a look at the relevant code of postSingleEvent(Object event, PostingThreadState postingState):

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    
    
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        if (eventInheritance) {
    
    
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
    
    
                Class<?> clazz = eventTypes.get(h);
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
            }
        } else {
    
    
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        if (!subscriptionFound) {
    
    
            if (logNoSubscriberMessages) {
    
    
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
    
    
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

  eventClass The type Class of the event, subscriptionFound represents whether the event is published. Here you can see the function of eventInheritance, which is to call lookupAllEventTypes(eventClass) to find the Class of its own class, as well as the related Class of its inherited parent class and interface. As long as these matching event objects are found, they will be called.
  Then call postSingleEventForEventType(event, postingState, clazz) for all found event types. Take a look at its code:

    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    
    
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
    
    
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
    
    
            for (Subscription subscription : subscriptions) {
    
    
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted;
                try {
    
    
                    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;
    }

  It gets information about the subscription methods that need to be executed from subscriptionsByEventType. If found, traverse the subscriptions, get the subscription, and continue to call postToSubscription(subscription, event, postingState.isMainThread) to send the message.

    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 MAIN_ORDERED:
                if (mainThreadPoster != null) {
    
    
                    mainThreadPoster.enqueue(subscription, event);
                } else {
    
    
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(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);
        }
    }

  Here we start to deal with the subscription method. Before processing, you also need to see if thread scheduling is to be generated. For example, the threadMode configured here is MAIN, which means that it needs to be executed in the main thread. We have judged whether the calling thread is in the main thread before, and now it is controlled by the variable isMainThread. If isMainThread is true, invokeSubscriber(subscription, event) is called directly, and if isMainThread is false, mainThreadPoster.enqueue(subscription, event) is called.

thread scheduling

Let's talk about how it realizes execution scheduling by choosing to configure the call of the main thread.
  First of all, if it is currently in the main thread and does not generate thread scheduling, directly execute invokeSubscriber(subscription, event):

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

  Directly call the invoke method of Method to implement method execution. subscription.subscriber is the subscriber object, event is the event, and subscription.subscriberMethod.method is the subscription method.
  Look at it is not currently in the main thread, call mainThreadPoster.enqueue(subscription, event). mainThreadPoster is generated by mainThreadSupport.createPoster(this). We know that mainThreadSupport is a DefaultAndroidMainThreadSupport class object. Take a look at CreatePoster(this) of DefaultAndroidMainThreadSupportd

    @Override
    public Poster createPoster(EventBus eventBus) {
    
    
        return new HandlerPoster(eventBus, Looper.getMainLooper(), 10);
    }

  So mainThreadPoster is a HandlerPoster object. Take a look at HandlerPoster's enqueue(subscription, event)

    public void enqueue(Subscription subscription, Object event) {
    
    
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
    
    
            queue.enqueue(pendingPost);
            if (!handlerActive) {
    
    
                handlerActive = true;
                if (!sendMessage(obtainMessage())) {
    
    
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

  HandlerPoster inherits the Handler class. It encapsulates the call information and events into a PendingPost object and puts it in the queue. Then it calls sendMessage(obtainMessage()) to send messages here, and implement thread scheduling through Android's messaging mechanism.
Look at HandlerPoster's handleMessage(Message msg):

    @Override
    public void handleMessage(Message msg) {
    
    
        boolean rescheduled = false;
        try {
    
    
            long started = SystemClock.uptimeMillis();
            while (true) {
    
    
                PendingPost pendingPost = queue.poll();
                if (pendingPost == null) {
    
    
                    synchronized (this) {
    
    
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
    
    
                            handlerActive = false;
                            return;
                        }
                    }
                }
                eventBus.invokeSubscriber(pendingPost);
                long timeInMethod = SystemClock.uptimeMillis() - started;
                if (timeInMethod >= maxMillisInsideHandleMessage) {
    
    
                    if (!sendMessage(obtainMessage())) {
    
    
                        throw new EventBusException("Could not send handler message");
                    }
                    rescheduled = true;
                    return;
                }
            }
        } finally {
    
    
            handlerActive = rescheduled;
        }
    }
}

  As you can see, get the PendingPost object from the queue, and then call eventBus.invokeSubscriber(pendingPost) to execute.

    void invokeSubscriber(PendingPost pendingPost) {
    
    
        Object event = pendingPost.event;
        Subscription subscription = pendingPost.subscription;
        PendingPost.releasePendingPost(pendingPost);
        if (subscription.active) {
    
    
            invokeSubscriber(subscription, event);
        }
    }

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

  Determine that the active of the Subscription object is true, which means it is active, and then execute its subscription method. In the end, the invoke() of the Method is also executed, and the call of the subscription method is executed.
  The switching of other thread modes is implemented by calling the thread pool of the EventBus object, so I won’t go into details here.

unregister subscriber

  Unsubscriber means that you do not want to subscribe to this event. At this time, you need to execute the EventBus.getDefault().unregister(this) method. Take a look at its code:

    /** Unregisters the given subscriber from all event classes. */
    public synchronized void unregister(Object subscriber) {
    
    
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
    
    
            for (Class<?> eventType : subscribedTypes) {
    
    
                unsubscribeByEventType(subscriber, eventType);
            }
            typesBySubscriber.remove(subscriber);
        } else {
    
    
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

    /** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
    private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    
    
        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 (subscription.subscriber == subscriber) {
    
    
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

  Mainly deal with typesBySubscriber and subscriptionsByEventType.
  Obtain which types of events the subscribers have subscribed to through typesBySubscriber, then traverse all event types, and then call unsubscribeByEventType(subscriber, eventType) to process the subscription event information in subscriptionsByEventType. After the traversal is complete, typesBySubscriber will remove the subscriber.
  unsubscribeByEventType(subscriber, eventType) also finds the subscription information class Subscription that matches the unsubscriber. Delete it subscription.active = false.

Summarize

  Analyzing the entire framework code, we know that EventBus realizes the registration and execution of the subscription method through reflection. Thread scheduling, priority, and sticky events of the subscription method can be set through annotations. It is indeed relatively simple to use, and the code looks relatively concise. It implements thread scheduling within itself, and users only need to set the thread scheduling mode of the subscription method.

think

  Thinking: The reflection mechanism will affect the performance of the program. If the subscriber object class is a class with many methods, including many methods of its parent class, it should have an impact. Like the previous example, MainActivity is an inherited FragmentActivity, and FragmentActivity inherits other classes. Do these all need to be traversed?
  Let's take a look at the method of finding subscription objects:

    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    
    
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
    
    
            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 {
    
    
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

  This loop will stop when findState.clazz is null. After querying the Class of the current MainActivity, it will go to findState.moveToSuperclass() to see how it finds the parent class:

        void moveToSuperclass() {
    
    
            if (skipSuperClasses) {
    
    
                clazz = null;
            } else {
    
    
                clazz = clazz.getSuperclass();
                String clazzName = clazz.getName();
                // Skip system classes, this degrades performance.
                // Also we might avoid some ClassNotFoundException (see FAQ for background).
                if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") ||
                        clazzName.startsWith("android.") || clazzName.startsWith("androidx.")) {
    
    
                    clazz = null;
                }
            }
        }

  See, if the parent class is a package name starting with "java." "javax.", "android.", "androidx.", then it is considered that the class provided by the system has been found. We generally do not define the class at the beginning of this kind of package by ourselves, such as the parent class FragmentActivity in the example, which is fully registered with androidx.fragment.app.FragmentActivity, so clazz will be set to null. Just jumped out of the loop.
  OK, that's the end of this article.

Guess you like

Origin blog.csdn.net/q1165328963/article/details/132199687