preface

EventBus is an excellent Android EventBus framework that can be used to simplify communication and data transfer between components, such as activities, fragments, child threads and UI threads. Based on publish/subscribe model, it can decouple business code and improve the efficiency of development.

This article briefly summarizes the use of EventBus 3.0, which I recently used for requirements and encountered some problems with sticky events.

The basic concept

EventBus consists of three main elements:

  • Event
  • Publisher
  • Subscriber

Events can be of any type. They can be divided into ordinary events and StickyEvent events, which are described separately later in this article.

Publisher, which can call the POST method from anywhere in any thread to send an event.

Event Subscriber/event receiver, create a method to receive the event for processing at the location that needs to receive the event (usually Activity or Fragment, depending on the service). After EventBus 3.0, the method name can be specified at will. However, you need to add an @SUBSCRIBE annotation, which specifies the thread model, whether sticky events are supported, and the priority.

Simple usage

There are a few steps to get started using EventBus quickly:

0. Add dependencies

Add a dependency to EventBus in Gradle:

implementation 'org. Greenrobot: eventbus: 3.1.1'
Copy the code

1. Define an event

Generally speaking, events are defined according to the business. To clarify the meaning, events are added to indicate that the Event is an EventBus Event, such as LoginEvent and LogoutEvent, which correspond to events sent after login and logout respectively.

Here we can define a StringEvent that carries a String variable:

class StringEvent{ public String msg; public StringEvent(String msg){ this.msg = msg; }}Copy the code

2. Register and deregister EventBus

Subscribers need to register/unregister in the bus. Only after registration can subscribers normally receive events. Normally we subscribe to events in activities, fragments, and need to do this in the lifecycle callback function.

  • Activity: Register in onCreate and unregister in onDestory.
Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    EventBus.getDefault().register(this);
}
Copy the code
@Override protected void onDestroy() {
    super.onDestroy();
    EventBus.getDefault().unregister(this);
}
Copy the code
  • Fragment: Register in onCreateView, unregister in onDestoryView.
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    EventBus.getDefault().register(this);
    return super.onCreateView(inflater, container, savedInstanceState);
}
Copy the code
@Override public void onDestroyView() {
    super.onDestroyView();
    EventBus.getDefault().unregister(this);
}
Copy the code

The official documentation for EventBus recommends registering and unregistering in onStart and onStop, noting that this period works for most cases. During the development process, you will also encounter situations where onStart registration is late and events are not received, so you can also register slightly earlier.

3. Send events

Sending events is as simple as calling a POST method.

EventBus.getDefault().post(new StringEvent("hello"));
Copy the code

4. Handle events

Define a method in an Activity or Fragment with an arbitrary name, an Event object to receive, and the @SUBSCRIBE annotation. Annotations can include thread models, support for sticky events, and priority attributes. The threading model is required, and the default value is POSTING. The concept of a threading model is introduced later in the example.

Public void onStringEvent(StringEvent event) {// business logic}Copy the code

Complete sample code

The following post a simple example code, combined with the demonstration effect should be better to understand.

MainActivity

public class MainActivity extends AppCompatActivity {

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EventBus.getDefault().register(this);

        mTextView = findViewById(R.id.textview);

        new Thread(new Runnable() {
            @Override public void run() {
                try {
                    Thread.sleep(3000);
                    EventBus.getDefault().post(new StringEvent("I'm receiving the event.")); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } @Subscribe(threadMode = ThreadMode.MAIN) public void onStringEvent(StringEvent event) { mTextView.setText(event.text);  } @Override protected voidonDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); }}Copy the code

activity_main.xml

<? xml version="1.0" encoding="utf-8"? > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Initial copy"
        android:layout_centerInParent="true"
        android:textSize="20sp"
        android:id="@+id/textview"/>
</RelativeLayout>
Copy the code

The logic of the code is very simple. There is a TextView in MainActivity, and an initial copy. A child thread is created, and an event is sent after 3s, and the contents of the TextView are modified after receiving the event.

The effect is as follows:

Threading model

This section introduces the following threading models of EventBus, which are used to specify in which threads the subscriber processes the event.

There are several thread models:

  1. ThreadMode.POSTING

By default, the subscriber is called in the same thread that publishes the event. Event delivery is done synchronously, and once the event is published, all subscribers receive the event, with minimal overhead because there is no thread switching. This model is most recommended for simple tasks.

@Subscribe(threadMode = ThreadMode.POSTING)
public void onMessage(MessageEvent event) {
    log(event.message);
}
Copy the code
  1. ThreadMode: MAIN

The subscriber will be called in Android’s main thread (UI thread). If the POSTING thread is the main thread, it is the same as POSTING. Event processing using this mode must not have time-consuming operations and must return quickly, otherwise it is easy to block the main thread and cause ANR.

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage(MessageEvent event) {
    textText.setText(event.message);
}
Copy the code
  1. ThreadMode: MAIN_ORDERED

Under this model, subscribers will also be called on the Android main thread. Unlike MAIN, the subscriber processes and receives the event sequentially, and the second subscriber needs to be processed by the first subscriber before receiving the event, so it is called ordered. This model also avoids blocking the main thread.

@Subscribe(threadMode = ThreadMode.MAIN_ORDERED)
public void onMessage(MessageEvent event) {
    textText.setText(event.message);
}
Copy the code
  1. ThreadMode: BACKGROUND

As the name suggests, this model handles events in the background. If an event is published in a child thread, the subscriber is called in that child thread for processing; If the event is published in the main thread, a new child thread is created and processed in the child thread.

@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void onMessage(MessageEvent event){
    saveToDisk(event.message);
}
Copy the code
  1. ThreadMode: ASYNC

Under this model, processing events are always fetched in a new thread, separate from the publishing thread and the main thread. Use this model if there are time-consuming operations in the event handling method, such as network requests, and so on. EventBus uses thread pools to reuse threads and save overhead.

@Subscribe(threadMode = ThreadMode.ASYNC)
public void onMessage(MessageEvent event){
    backend.send(event.message);
}
Copy the code

StickyEvent

Normal events must be registered and sent before they can be received by subscribers. In some cases, we want the subscriber to receive the event even if the event is sent first and then registered. EventBus provides sticky events to satisfy this situation.

When publishing an event, use the postSticky method to send a sticky event instead.

EventBus.getDefault().postSticky(new StringEvent("Hello World!"));
Copy the code

Then add sticky=true to the @SUBSCRIBE annotation of the subscriber receiving event method to support receiving sticky events.

@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onStringEvent(StringEvent event) {   
    mTextView.setText(event.message);
}
Copy the code

Here is another simple example to demonstrate the use of sticky events:

MainActivity:

public class MainActivity extends AppCompatActivity {

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getSupportActionBar().setTitle("MainActivity");
        mTextView = findViewById(R.id.textview);
    }

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void onStringEvent(StringEvent event) {
        mTextView.setText(event.text);
    }

    @Override protected void onDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); } public void register(View view) { EventBus.getDefault().register(this); } public void toSecondActivity(View view) { startActivity(new Intent(MainActivity.this, SecondActivity.class)); }}Copy the code

SecondActivity

public class SecondActivity extends AppCompatActivity {
    @Override protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        getSupportActionBar().setTitle("SecondActivity");
    }

    public void sendEvent(View view){
        EventBus.getDefault().post(new StringEvent("OWEN"));
        finish();
    }

    public void sendStickyEvent(View view){
        EventBus.getDefault().postSticky(new StringEvent("Sticky OWEN")); finish(); }}Copy the code

Effect demonstration:

The logic of the code is very simple. Instead of registering EventBus in onCreate, MainActivity places it in a button click event.

There are three events sent in the demo:

  1. When unregistered, send a normal event from SecondActivity and find the TextView text unchanged;
  2. If not registered, send Sticky event from SecondActivity, go back to MainActivity and click Register EventBus.
  3. After registering, send a normal event from SecondActivity and notice that the text changes again.

This is what the Sticky event does, send the event first, then register the subscriber, can also receive the event.

Use attention

The sent sticky events are saved in a map so that they can be received after subscribing, while keeping the last sticky event. For some events that are handled only once, the last sticky event will be processed repeatedly. This is not expected, and you need to manually remove the sticky event after processing. Use the removeStickyEvent method.

StringEvent stickyEvent = EventBus.getDefault().getStickyEvent(StringEvent.class); // Determine whether the sticky event existsif(stickyEvent ! Eventbus.getdefault ().removesticKyevent (stickyEvent); }Copy the code

ProGuard obfuscation rules

-keepattributes *Annotation*
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}
Copy the code