[JAVA] How to use EventBus3 and ThreadMode

EventBus This page explains how to use EventBus3, how to create an EventBus instance with a scope other than EventBus.getDefault (), which is not often mentioned, and ThreadMode, which is convenient but needs attention. I think you should read the following about why you use EventBus in the first place. Using the event bus on Android

I also arrived at the above at first, but when I read the contents to use it, it was the description of EventBus2, and I found that the usage is different in EventBus3, so I will write an example in EventBus3. Also, since the example posted was a little different from the purpose I wanted to use, I will also describe what I should do for my purpose. ** It seems that there is a widespread misconception that EventBus can only be used in the global scope **, so I will also mention that it is not the case. In addition, ** ThreadMode can be specified to control the execution thread, which I think is a very convenient function, but I found that it is necessary to be careful about its behavior **, so I read the source code and found out. Also describe the points.

How to use

It's easy to use as follows. However, even on the official page and other sites, there are only use cases where other classes notify Activity, and I could not find a use case where Activity notifies other classes, so I will write it in that case.

Describe the following in build.gradle.

build.gradle


compile 'org.greenrobot:eventbus:3.0.0'

Create a class for the event

MessageEvent.java


public class MessageEvent {
    private int eventType
    public MessageEvent(int type) {
        this.eventType = type;
    }

    public int getEventType() {
        return eventType;
    }
}

Class you want to be notified

Subscriber.java


public class Subscriber {
    @Subscribe
    public void onEvent(MessageEvent event) {
        // do something
    }
}

Send notification MainActivity

MainActivity.java


public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {
    private Subscriber mSubscriber;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSubscriber = new Subscriber();
    }

    @Override
    protected void onStart() {
        super.onStart();
        EventBus.getDefault().register(mSubscriber);
    }

    @Override
    protected void onStop() {
        super.onStop();
        EventBus.getDefault().unregister(mSubscriber);
    }

    @SuppressWarnings("StatementWithEmptyBody")
    @Override
    public boolean onNavigationItemSelected(MenuItem item) {
        // Send event
        EventBus.getDefault().post(new MessageEvent(item.getItemId()));

        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
        drawer.closeDrawer(GravityCompat.START);
        return true;
    }
}

The point is to register the instance you want to be notified by using ʻEventBus.getDefault (). Register (). EventBus internally holds a set of registered instances, and when ʻEventBus.getDefault (). Post () is called, each instance set becomes an argument at the time of post. We are checking if there is a method that @Subscribes the type of the class, and if there is a corresponding method, we will notify you by calling that method.

In this example EventBus.getDefault().post(new MessageEvent(item.getItemId())); Is called, so the method that takes MessageEvent as an argument is called from the methods for which @Subscribe is defined in the instance registered in EventBus (only mSubscriber this time).

Definition of EventBus instances with different scopes

The official pages and articles I've seen only show examples using the default EventBus instance with ʻEventBus.getDefault (). ʻIf you use only EventBus.getDefault () , all the instances registered byregister ()will be the scope of the post destination. This is easy, but it goes against the rule of thumb that the scope of an object should be as narrow as possible, and as the number of classes increases, it becomes difficult to know which method is actually called.

Little-known, in fact, ʻEventBus.getDefault ()is just a convenience method that returns a singleton instance of EventBus, and the user creates a different EventBus instance by runningnew EventBus (). It is also possible. Since the instance that is register ()is not shared between EventBus instances, set the scope for each EventBus instance byregister ()` only a specific instance to the generated EventBus instance. Can be done.

The following is an example of creating an EventBus instance for ViewModel1 and an EventBus instance for ViewModel2.

DifferentScopeActivity.java


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mEventBusForViewModel1 = new EventBus();
        mEventBusForViewModel2 = new EventBus();

        mViewModel1 = new ViewModel1();
        mViewModel2 = new ViewModel2();
    }

    @Override
    protected void onStart() {
        super.onStart();
        mEventBusForViewModel1.register(mViewModel1);
        mEventBusForViewModel2.register(mViewModel2);
    }

    @Override
    protected void onStop() {
        super.onStop();
        mEventBusForViewModel1.unregister(mViewModel1);
        mEventBusForViewModel2.unregister(mViewModel2);
    }

Even in the announcement that was very popular at DroidKaigi 2017 below

MVVM Architecture realized by DataBinding

EventBus is global so it's hard to tell where notifications come from

I think that the presenter is probably aware that there is only a way to use it using ʻEventBus.getDefault ()`. I think that there is no known way to create a new Event Bus. (I would like to point out that the criticism of the above announcement is not clear. I have given it as an example of a common misunderstanding.)

ThreadMode By specifying ThreadMode in @Subscribe, you can set which thread to execute.

POSTING If @Subscribe (threadMode = ThreadMode.POSTING) is specified, the thread will be interrupted and operated in the same thread as the thread that performed ** post. Therefore, the post will not be returned until the method that is @Subscribe is finished. ** ** Please note that if you post from MainThread and the @ Subscribe side does heavy processing, it will be ANR. In the case of (new Handler ()). post, the post action should have ended immediately, which is different.

--post side: MainThread-> subscribe side: MainThread --post side: SubThread1-> subscribe side: SubThread1

MAIN If you specify @Subscribe (threadMode = ThreadMode.MAIN), it will always be executed in the process's Main Thread. ** If the posting side is also running on Main Thread, it will run by interrupting the thread as if POSTING was specified. Therefore, the post does not end until the method that is @Subscribe ends. ** **

--post side: MainThread-> subscribe side: MainThread --post side: SubThread1-> subscribe side: MainThread

BACKGROUND If @Subscribe (threadMode = ThreadMode.BACKGROUND) is specified, the process will be executed by threads other than Main Thread. ** If the post side is not MainThread, it will operate by interrupting the thread as if POSTING was specified. Therefore, the post will not be returned until the method that is @Subscribe is finished. ** **

If post execution is MainThread

ExecutorService service = Executors.newCachedThreadPool();
service.execute(runnable);

The processing is almost the same as that of. This ensures that each process is executed in parallel (unless a very large number of posts are executed), so there is no block between the methods that are @Subscribe.

--post side: MainThread-> subscribe side: SubThread2 or 3 or 4 or ... --post side: SubThread1-> subscribe side: SubThread1

Precautions for specifying BACKGROUND

If the post side is MainThread, ** @ Subscribe side processing is not serialized (serialized) **, so if there is a resource to be protected inside the method that is @ Subscribe Must be properly protected.

To give an example

Subscriber.java


int resource = 0;

@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void handleEvent() {
    resource++;
}

Publisher.java


private void postManyEvents() {
    for(int i=0; i<10000; i++) {
        EventBus.getDefault().post();
    }
}

There is no guarantee that the Subscriber resource will be 10000 if you post 10000 times in the form of (because the ++ operator does not work atomically). To solve this problem, use an atomic class as shown below, or protect it with a syncronized block or the like.

Subscriber.java


AtomicInteger resource = new AtomicInteger(0);

@Subscribe(threadMode = ThreadMode.BACKGROUND)
public void handleEvent() {
    resource.incrementAndGet();
}

Publisher.java


private void postManyEvents() {
    for(int i=0; i<10000; i++) {
        EventBus.getDefault().post();
    }
}

ASYNC If @Subscribe (threadMode = ThreadMode.ASYNC) is specified, the process will always be executed in the background thread regardless of what the posted thread is. This is the same behavior as the BACKGROUND specification when the post side is Main Thread. In other words, the same precautions as when specifying BACKGROUND are required.

--post side: MainThread-> subscribe side: SubThread2 or 3 or 4 or ... --post side: SubThread1-> subscribe side: SubThread2 or 3 or 4 or ...

Use of Thread Mode properly

It is better not to perform processing in MainThread as much as possible, but it is difficult to use properly because data inconsistency or exception occurs in unexpected places if all threads are different threads. I think that using only two, MainThread and WorkerThread, is the best balance between safety and response, so basically the policy of specifying POSTING and throwing processing to WorkerThread in the method is the best. It may be safe. If there is a process that needs to be executed in MainThread on the @ Subscribe side, it will be necessary to specify MAIN. Also, I think it would be better to specify ASYNC for processing that does not cause any problems even if it operates in parallel.

Finally

Described EventBus3, which releases Android code from callback hell. Note that ThreadMode behaves unexpectedly unintuitively.

If you specify @WorkerThread or something like that, it seems to be easier to use personally if you work in series with a single SubThread, so let's make it yourself.

Recommended Posts

How to use EventBus3 and ThreadMode
How to use StringBurrer and Arrays.toString.
How to use equality and equality (how to use equals)
How to use OrientJS and OrientDB together
How to set up and use kapt
How to use substring and substr methods
How to use @Builder and @NoArgsConstructor together
How to use Map
How to use rbenv
How to use letter_opener_web
How to use with_option
How to use fields_for
How to use java.util.logging
How to use map
How to use collection_select
How to use Twitter4J
How to use active_hash! !!
How to use MapStruct
How to use hidden_field_tag
How to use TreeSet
[How to use label]
How to use identity
How to use hashes
How to use JUnit 5
How to use Dozer.mapper
How to use Gradle
How to use org.immutables
How to use java.util.stream.Collector
How to use VisualVM
How to use Map
[Ruby] How to use gsub method and sub method
How to use Segmented Control and points to note
How to use scope and pass processing (Jakarta)
[Java] How to use Calendar class and Date class
[Java] How to use Map
How to use Chain API
[Java] How to use Map
How to use Priority Queuing
[Rails] How to use enum
How to use java Optional
How to use JUnit (beginner)
How to use Ruby return
[Rails] How to use enum
How to use @Builder (Lombok)
[Swift] How to use UserDefaults
How to use java class
How to use Swift UIScrollView
How to use Big Decimal
[Java] How to use Optional ②
[Java] How to use removeAll ()
How to use String [] args
[Java] How to use string.format
How to use rails join
How to use Java Map
Ruby: How to use cookies
How to use dependent :: destroy
How to use Eclipse Debug_Shell
How to use Apache POI
[Rails] How to use validation
How to use Java variables
[Rails] How to use authenticate_user!