While using EventBus, I have faced an unexpected situation that a subscription method of a Fragment is called one time even after the Fragment is unregistered.
Scenario is like this. I have an Activity containing a layout which any Fragment can be put. The Activity starts with a certain Fragment.
The Activity and the Fragment register themselves on their onResume()
and unregister on onPause()
. They have their own handlers for each for an event of same type.
By a dispatched event, the Activity replaces the Fragment with another by some condition. Then, the Fragment's onPause()
gets called during removal process and EventBus.getDefault().unregister(this)
is also executed.
Here, then, I expect the Fragment's handler not to be called from now. BUT it is called one time right after the Fragment is unregistered.
It seems like that EventBus doesn't handle the case of any subscriber is unregistered while event posting process. Anyone knows about this issue?
Edited for more details
Related methods in the Fragment
@Override
public void onResume() {
Log.d(LOG_TAG, "onResume()");
super.onResume();
EventBus.getDefault().register(this);
Communicator.registerListener(listener);
}
@Override
public void onPause() {
Log.d(LOG_TAG, "onPause()");
Communicator.unregisterListener(listener);
EventBus.getDefault().unregister(this);
super.onPause();
}
@Subscribe(sticky = true)
public void handleEvent(DeviceConnectionSelectEvent event) {
if (event.container != null) {
setDevice(event.container.getRapaelDevice());
}
}
Call stack of onPause()
Fragment.onPause()
at com.neofect.rapael.client.bridge.app.device.kids.SmartKidsSensorDataFragment.onPause(SmartKidsSensorDataFragment.java:105)
at android.support.v4.app.Fragment.performPause(Fragment.java:2139)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1117)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1252)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1234)
at android.support.v4.app.FragmentManagerImpl.dispatchPause(FragmentManager.java:2060)
at android.support.v4.app.Fragment.performPause(Fragment.java:2135)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1117)
at android.support.v4.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1349)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:695)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1617)
at android.support.v4.app.FragmentManagerImpl.executePendingTransactions(FragmentManager.java:570)
at com.neofect.rapael.client.bridge.app.MainActivity.changeDeviceDetailFragment(MainActivity.java:111)
at com.neofect.rapael.client.bridge.app.MainActivity.handleEvent(MainActivity.java:100)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at org.greenrobot.eventbus.EventBus.invokeSubscriber(EventBus.java:485)
at org.greenrobot.eventbus.EventBus.postToSubscription(EventBus.java:416)
at org.greenrobot.eventbus.EventBus.postSingleEventForEventType(EventBus.java:397)
at org.greenrobot.eventbus.EventBus.postSingleEvent(EventBus.java:370)
at org.greenrobot.eventbus.EventBus.post(EventBus.java:251)
at org.greenrobot.eventbus.EventBus.postSticky(EventBus.java:292)
at com.neofect.rapael.client.bridge.app.ui.device_connection_list.DeviceConnectionListPresenter.selectConnectionItem(DeviceConnectionListPresenter.java:114)
at com.neofect.rapael.client.bridge.app.ui.device_connection_list.DeviceConnectionListPresenter.access$400(DeviceConnectionListPresenter.java:28)
at com.neofect.rapael.client.bridge.app.ui.device_connection_list.DeviceConnectionListPresenter$1.onDeviceReady(DeviceConnectionListPresenter.java:179)
at com.neofect.rapael.client.bridge.app.ui.device_connection_list.DeviceConnectionListPresenter$1.onDeviceReady(DeviceConnectionListPresenter.java:122)
at com.neofect.communicator.CommunicationHandler$5.run(CommunicationHandler.java:90)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5017)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(Native Method)
Call stack of handleEvent()
- This is called right after above onPause()
Fragment.handleEvent()
at com.neofect.rapael.client.bridge.app.device.kids.SmartKidsSensorDataFragment.handleEvent(SmartKidsSensorDataFragment.java:121)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at org.greenrobot.eventbus.EventBus.invokeSubscriber(EventBus.java:485)
at org.greenrobot.eventbus.EventBus.postToSubscription(EventBus.java:416)
at org.greenrobot.eventbus.EventBus.postSingleEventForEventType(EventBus.java:397)
at org.greenrobot.eventbus.EventBus.postSingleEvent(EventBus.java:370)
at org.greenrobot.eventbus.EventBus.post(EventBus.java:251)
at org.greenrobot.eventbus.EventBus.postSticky(EventBus.java:292)
at com.neofect.rapael.client.bridge.app.ui.device_connection_list.DeviceConnectionListPresenter.selectConnectionItem(DeviceConnectionListPresenter.java:114)
at com.neofect.rapael.client.bridge.app.ui.device_connection_list.DeviceConnectionListPresenter.access$400(DeviceConnectionListPresenter.java:28)
at com.neofect.rapael.client.bridge.app.ui.device_connection_list.DeviceConnectionListPresenter$1.onDeviceReady(DeviceConnectionListPresenter.java:179)
at com.neofect.rapael.client.bridge.app.ui.device_connection_list.DeviceConnectionListPresenter$1.onDeviceReady(DeviceConnectionListPresenter.java:122)
at com.neofect.communicator.CommunicationHandler$5.run(CommunicationHandler.java:90)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5017)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(Native Method)
The handleEvent()
is called on the same call stack as onPause()
according to the above. They are called in one event posting.
I'm guessing that the event dispatching loop (message queue loop) keeps the subscription list BEFORE starting posting, so it cannot handle the subscription removal done during posting process.
You're right.
If you look into the source code of Event Bus, you will see that for the same threads (MAIN in your case) Event Bus doesn't recheck if a subscription is active during posting process:
Inside this method, Event Bus reaches postSingleEventForEventType()
where it gets all subscriptions available at the moment and notifies them. Those that are from the same thread, are executed with no check:
EventBus#invokeSubscriber(subscription, event)
the others are scheduled and will be executed with a method that does checks:
EventBus#invokeSubscriber(pendingPost)
Inside this method, Event Bus reaches unsubscribeByEventType()
where it gets the same subscriptions, deletes and deactivates the corresponding one:
List<Subscription> subscriptions = subscriptionsByEventType.get(eventClass);
for (...) {
// ...
subscription.active = false;
subscriptions.remove(i);
}