EventBus usage and source code analysis of Android system

Posted by blueman378 on Tue, 21 Sep 2021 06:54:43 +0200

1. Introduction to eventbus

EventBus is a publish / subscribe event bus framework used in Android development. Based on the observer mode, it separates the receiver and sender of events, simplifies the communication between components, especially the communication between fragments, and can avoid many inconveniences caused by broadcast communication.
Simple to use, high efficiency and small size! Below is the official schematic diagram of EventBus:

2. How to use eventbus

2.1 use steps

a. Add dependent Library
Add in the build.gradle file corresponding to the project

compile 'org.greenrobot:eventbus:3.0.0'

b. Register

EventBus.getDefault().register(this);

c. Write off

EventBus.getDefault().unregister(this);

d. Construct send event class

public class MyEvent {
    private int mMsg;
    public MyEvent(int msg) {
        mMsg = msg;
    }
    public int getMsg(){
        return mMsg;
    }
}

e. Publish event

EventBus.getDefault().post(new MyEvent(time));

f. Receive event

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(MyEvent event){
	progressBar.setProgress(event.getMsg());
}

onEvent is Subscriber and MyEvent is the event type to listen to

2.2 use examples

Implement A progress bar, thread A sends the progress percentage, and thread B receives the progress percentage and displays it
reference resources: https://blog.csdn.net/bzlj2912009596/article/details/81664984
a. build.gradle

dependencies {
	//1. Add dependent Library
    compile 'org.greenrobot:eventbus:3.0.0'
}

b. activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.wgh.eventbusdemo.MainActivity"
    android:orientation="vertical">
 
    <ProgressBar
        android:id="@+id/progressbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="150dp"
        android:max="100"
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"/>
    <Button
        android:id="@+id/button"
        android:layout_marginTop="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start downloading"/>
</LinearLayout>

c. MainActivity.java

package com.example.wgh.eventbusdemo;
 
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ProgressBar;
 
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
 
 
public class MainActivity extends Activity {
 
    public ProgressBar progressBar = null;
    public int time = 0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while (time<100){
                            time += 15;
							//4. Send message
                            EventBus.getDefault().post(new MyEvent(time));
                            try {
                                Thread.sleep(200);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }).start();
            }
        });
        progressBar = (ProgressBar) findViewById(R.id.progressbar);
		//2. Register EventBus
        EventBus.getDefault().register(this);
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
		//6. Log off EventBus
        EventBus.getDefault().unregister(this);
    }
	//5. Receive message
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEvent(MyEvent event){
        progressBar.setProgress(event.getMsg());
    }
}

d. MyEvent.java

//3. Construct send message class
public class MyEvent {
    private int mMsg;
    public MyEvent(int msg) {
        mMsg = msg;
    }
    public int getMsg(){
        return mMsg;
    }
}

Note: the function parameters of the sent message and the received message must be consistent, otherwise the function receiving the message cannot receive the message
For example, send a message: EventBus.getDefault().post(new MyEvent1(time)); When, only message function 1 can receive messages.
Receive message function 1: public void onEvent(MyEvent1 event) {...}
Receive message function 2: public void onEvent(MyEvent2 event) {...}

3. Principle of eventbus

3.1 Subscribe comments

Eventbus 3.0 starts to configure the event subscription method with the Subscribe annotation, and the method name is no longer used

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    // Specifies the thread mode of the event subscription method, that is, the thread that executes the event subscription method to process events. The default is POSTING
    ThreadMode threadMode() default ThreadMode.POSTING;
    // Whether sticky events are supported. The default value is false
    boolean sticky() default false;
    // Specifies the priority of the event subscription method, which is 0 by default. If multiple event subscription methods can receive the same event, the one with higher priority will receive the event first
    int priority() default 0;
}
  • ThreadMode.POSTING is the default thread mode. Events sent in that thread are processed in the corresponding thread, avoiding thread switching and high efficiency.
  • ThreadMode.MAIN. If an event is sent in the main thread (UI thread), the event is directly processed in the main thread; If an event is sent in a child thread, the event is queued first, and then the Handler switches to the main thread to process the event in turn.
  • ThreadMode.MAIN_ORDERED: no matter which thread sends events, first put the events into the queue, and then switch to the main thread through the Handler to process the events in turn.
  • Threadmode.backgroup: if events are sent in the main thread, the events will be queued first, and then the events will be processed in turn through the thread pool; If an event is sent on a child thread, the event is processed directly on the thread that sent the event.
  • ThreadMode.ASYNC: no matter which thread sends events, the events are queued and processed through the thread pool.

3.2 relationship between subscribers and Events

Registered subscribers will be saved to subscriptionsByEventType and typesBySubscriber
a. subscriptionsByEventType

subscriptionsByEventType is a HashMap. key is the event type eventType of registered subscribers listening to events, and value is all subscribers listening to the same event type eventType
b. typesBySubscriber

typesBySubscriber is a HashMap. The key is the MainActivity of the current class of registered subscribers, and the value is all types of subscribers contained in the current class

3.3 viscous events

Generally, we use EventBus to prepare the method of subscribing to events, then register events, and finally send events, that is, we need to have the receiver of the event first. However, sticky events are just the opposite. We can send events first, and then prepare methods for subscribing to events and registering events. This event is called sticky events.
That is, for general events, first register the Subscriber listening to an event, and then send this event; However, a sticky event is to send an event first and then register the Subscriber of the event

4. Analysis of eventbus source code

4.1 register registration events

EventBus.getDefault().register(this);

Source code analysis:

EventBus.java (frameworks\base\packages\systemui\src\com\android\systemui\recents\events)	39817	2017/8/25
public class EventBus extends BroadcastReceiver {
    public static EventBus getDefault() {
        if (sDefaultBus == null)
        synchronized (sLock) {
            if (sDefaultBus == null) {
                if (DEBUG_TRACE_ALL) {
                    logWithPid("New EventBus");
                }
                sDefaultBus = new EventBus(Looper.getMainLooper());
            }
        }
        return sDefaultBus;
    }
}

public void register(Object subscriber) {
	Class<?> subscriberClass = subscriber.getClass();	//Get the class of the currently registered Subscriber, here is MainActivity
	List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);	//The function that gets all registered events in the current class, that is, the function that gets all Subscribe annotations
	synchronized (this) {
		for (SubscriberMethod subscriberMethod : subscriberMethods) {
			subscribe(subscriber, subscriberMethod);	//Loop register all events
		}
	}
}

subscriber is MainActivity, and subscriberMethods is a collection of methods that save all subscription events in the currently registered class MainActivity and its parent class

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
	Class<?> eventType = subscriberMethod.eventType;	//Get the parameter type of the current registered event, and distinguish different events according to the parameter type
	Subscription newSubscription = new Subscription(subscriber, subscriberMethod);	//1. Create a Subscription corresponding to the current listening event
	CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);	//1. Key: subscriptionsByEventType is a HashMap, key is the eventType event type, and value is all functions that listen to the same event type
	if (subscriptions == null) {
		subscriptions = new CopyOnWriteArrayList<>();
		subscriptionsByEventType.put(eventType, subscriptions);
	}
	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);		//1. Save the registered Subscriber to subscriptionsByEventType
			break;
		}
	}
	//-------------------------------------------
	List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);	//2. Key: typesBySubscriber is a HashMap. The key is the MainActivity of the current class of registered subscribers, and the value is all types of subscribers contained in the current class
	// If it does not exist, create a subscribedEvents and save it to typesBySubscriber
	if (subscribedEvents == null) {
		subscribedEvents = new ArrayList<>();
		typesBySubscriber.put(subscriber, subscribedEvents);
	}
	subscribedEvents.add(eventType);	//2. Save the event type eventType corresponding to the registered Subscriber to typesBySubscriber
	// Related to viscous events, which will be analyzed later
	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);
		}
	}
}

Registered subscribers will be saved to subscriptionsByEventType and typesBySubscriber
subscriptionsByEventType is a HashMap. key is the event type eventType of registered subscribers listening to events, and value is all subscribers listening to the same event type eventType
typesBySubscriber is a HashMap. The key is the MainActivity of the current class of registered subscribers, and the value is all types of subscribers contained in the current class

4.2 post sending event

EventBus.getDefault().post(new MyEvent(time));

Source code analysis:

public void post(Object event) {
	// currentPostingThreadState is a ThreadLocal of PostingThreadState type
	// The PostingThreadState class holds information such as event queues and thread patterns
	PostingThreadState postingState = currentPostingThreadState.get();
	List<Object> eventQueue = postingState.eventQueue;
	// Add the event to be sent to the event queue
	eventQueue.add(event);
	// isPosting defaults to false
	if (!postingState.isPosting) {
		// Whether to be the main thread
		postingState.isMainThread = isMainThread();
		postingState.isPosting = true;
		if (postingState.canceled) {
			throw new EventBusException("Internal error. Abort state was not reset");
		}
		try {
			// Traverse event queue
			while (!eventQueue.isEmpty()) {
				// Send a single event
				// eventQueue.remove(0) to remove events from the event queue
				postSingleEvent(eventQueue.remove(0), postingState);
			}
		} finally {
			postingState.isPosting = false;
			postingState.isMainThread = false;
		}
	}
}

Save the sent event to the eventQueue, traverse the eventQueue, and execute postSingleEvent to process the event

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
	Class<?> eventClass = event.getClass();
	boolean subscriptionFound = false;
	// eventInheritance is true by default, indicating whether to look up the parent class of the event
	if (eventInheritance) {
		// Find the Class of the current event type and save it to the collection together with the Class of the current event type
		List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
		int countTypes = eventTypes.size();
		// Traverse the Class collection and continue processing events
		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));
		}
	}
}

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
	CopyOnWriteArrayList<Subscription> subscriptions;
	synchronized (this) {
		// Gets the Subscription collection corresponding to the event type
		subscriptions = subscriptionsByEventType.get(eventClass);
	}
	// If you have subscribed to events of the corresponding type
	if (subscriptions != null && !subscriptions.isEmpty()) {
		for (Subscription subscription : subscriptions) {
			// Record events
			postingState.event = event;
			// Record the corresponding subscription
			postingState.subscription = subscription;
			boolean aborted = false;
			try {
				// Final event handling
				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;
}

According to the event type of the currently sent event, get all subscribers listening to this event type from subscriptionsByEventType, traverse these subscribers and execute postToSubscription for further processing, that is, the methods corresponding to all subscribers listening to this event will be called

4.3 event handling

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
	// Determine the thread mode of the subscription event method
	switch (subscription.subscriberMethod.threadMode) {
		// The default thread mode is to send events in that thread and process events in that thread
		case POSTING:
			invokeSubscriber(subscription, event);
			break;
		// Handling events in the main thread
		case MAIN:
			// If an event is sent on the main thread, the event is processed directly on the main thread through reflection
			if (isMainThread) {
				invokeSubscriber(subscription, event);
			} else {
				 // If an event is sent in a child thread, the event is queued, and the Handler switches to the main thread to process the event
				// mainThreadPoster is not empty
				mainThreadPoster.enqueue(subscription, event);
			}
			break;
		// No matter which thread sends events, first put the events into the queue, and then switch to the main thread through the Handler to process the events in turn.
		// mainThreadPoster is not empty
		case MAIN_ORDERED:
			if (mainThreadPoster != null) {
				mainThreadPoster.enqueue(subscription, event);
			} else {
				invokeSubscriber(subscription, event);
			}
			break;
		case BACKGROUND:
			// If events are sent in the main thread, the events are queued first, and then processed in turn through the thread pool
			if (isMainThread) {
				backgroundPoster.enqueue(subscription, event);
			} else {
				// If an event is sent in a child thread, the event is processed directly in the thread sending the event through reflection
				invokeSubscriber(subscription, event);
			}
			break;
		// No matter which thread sends the event, the event is queued and processed through the thread pool.
		case ASYNC:
			asyncPoster.enqueue(subscription, event);
			break;
		default:
			throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
	}
}

Determine how to handle the event according to the thread mode of the event subscription method and the thread sending the 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);
	}
}

Use reflection to execute the onEvent method of subscribing to the event, so that the sent event will be received by the Subscriber and processed accordingly

4.4 viscous events

EventBus.getDefault().postSticky(new MyEvent(time));

Source code analysis:

public void postSticky(Object event) {
	synchronized (stickyEvents) {
		stickyEvents.put(event.getClass(), event);
	}
	post(event);
}

Save the sent sticky events to stickyEvents

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
	......
	......
	......
	// If the sticky attribute of the Subscribe annotation of the method currently subscribing to the event is true, the method can accept sticky events
	if (subscriberMethod.sticky) {
		// The default value is true, indicating whether to look up the parent class of the event
		if (eventInheritance) {
			// stickyEvents is to save the event type and corresponding events when sending sticky events
			Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
			for (Map.Entry<Class<?>, Object> entry : entries) {
				Class<?> candidateEventType = entry.getKey();
				// If candidateEventType is a subclass of eventType or
				if (eventType.isAssignableFrom(candidateEventType)) {
					// Get the corresponding event
					Object stickyEvent = entry.getValue();
					// Handling sticky events
					checkPostStickyEventToSubscription(newSubscription, stickyEvent);
				}
			}
		} else {
			Object stickyEvent = stickyEvents.get(eventType);
			checkPostStickyEventToSubscription(newSubscription, stickyEvent);
		}
	}
}
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
	if (stickyEvent != null) {
		postToSubscription(newSubscription, stickyEvent, isMainThread());
	}
}

eventType is the event type monitored by the current Subscriber, and the sent sticky events are retrieved from stickyEvents. If the event type monitored by the registered Subscriber is exactly the event type of sticky events, checkPostStickyEventToSubscription is directly called for event processing

5. EventBus summary

  • Registration event: save the registered Subscriber to subscriptionsByEventType and typesBySubscriber
    subscriptionsByEventType is a HashMap. key is the event type eventType of registered subscribers listening to events, and value is all subscribers listening to the same event type eventType
    typesBySubscriber is a HashMap. The key is the MainActivity of the current class of registered subscribers, and the value is all types of subscribers contained in the current class
  • Send event: according to the event type of the current send event, get all subscribers listening to this event type from subscriptionsByEventType, traverse these subscribers and execute postToSubscription for further processing, that is, the methods corresponding to all subscribers listening to this event will be called
  • Event handling: use reflection to execute the onEvent method of subscribing to event, so that the sent event will be received by the Subscriber and processed accordingly

Good article:
https://www.jianshu.com/p/d9516884dbd4
https://www.6hu.cc/archives/215.html

Topics: Android eventbus