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.
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 @Subscribe
s 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).
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 running
new EventBus (). It is also possible. Since the instance that is
register ()is not shared between EventBus instances, set the scope for each EventBus instance by
register ()` 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
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 ...
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.
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