EventBus отправляет сообщения незарегистрированному подписчику

При использовании EventBus я столкнулся с неожиданной ситуацией, когда метод подписки фрагмента вызывается один раз даже после того, как фрагмент не зарегистрирован.

Сценарий такой. У меня есть действие, содержащее макет, в который можно поместить любой фрагмент. Активность начинается с определенного Фрагмента.

Активность и фрагмент регистрируются на своем onResume() и отменяются на onPause(). У них есть свои обработчики для каждого события одного типа.

По отправленному событию действие заменяет фрагмент другим по какому-либо условию. Затем во время процесса удаления вызывается onPause() фрагмента, а также выполняется EventBus.getDefault().unregister(this).

Итак, я ожидаю, что обработчик фрагмента больше не будет вызываться. НО он вызывается один раз сразу после отмены регистрации Фрагмента.

Похоже, что EventBus не обрабатывает случай, когда какой-либо подписчик не зарегистрирован во время процесса публикации события. Кто-нибудь знает об этой проблеме?


Отредактировано для более подробной информации

  • Связанные методы во фрагменте

    @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());
        }
    }
    
  • Стек вызовов из 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)
    
  • Стек вызовов handleEvent() — вызывается сразу после вышеуказанного 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)
    

handleEvent() вызывается в том же стеке вызовов, что и onPause(), согласно приведенному выше. Они называются в одной проводке события.

Я предполагаю, что цикл диспетчеризации событий (цикл очереди сообщений) сохраняет список подписок ДО начала публикации, поэтому он не может обработать удаление подписки, выполненное во время процесса публикации.


person neokim    schedule 24.05.2016    source источник
comment
Просто наивное предположение: вы звоните unregister() до или после super.onPause()?   -  person SqueezyMo    schedule 24.05.2016
comment
Можете ли вы опубликовать стек вызовов вашего onPause, где вы отменяете регистрацию?   -  person Markus Junginger    schedule 24.05.2016
comment
Я отредактировал для более подробной информации.   -  person neokim    schedule 25.05.2016
comment
Вам необходимо уточнить свой вопрос, чтобы указать, в каком потоке (A) ваша активность заменяет ваш фрагмент, (B) ваш фрагмент отменяет регистрацию и (C) событие, которое отправляется вашему обработчику событий. Из приведенной выше трассировки стека видно, что вы вызываете свои обработчики событий в потоке вызывающей стороны. Это может привести к доставке сообщений не по порядку, и IMHO следует всегда избегать, поскольку это означает, что вы связываете создание события с потреблением события, а это именно то, для чего был разработан EventBus.   -  person William    schedule 08.06.2016
comment
@William Вы ошиблись. Обработчик событий не вызывается приложением, обработчик событий вызывается EventBus неявно. И разделение создания и потребления событий, о котором вы упомянули, — это то, что я ищу от использования EventBus. Меня не волнует, когда и где перезванивают другие абоненты, регистрируются и отменяются при определенном событии.   -  person neokim    schedule 08.06.2016
comment
В этом случае, видимо, подписчику нужно проверить, вызывается ли он в стеке постпроцесса события или нет. Потому что, если подписчик отменяет регистрацию в каком-либо постпроцессе события, нет гарантии, что его отмена регистрации будет обработана.   -  person neokim    schedule 08.06.2016
comment
@neo.kim, ты не понял меня. Механизм обработки по умолчанию для EventBus заключается в доставке события в поток постера.   -  person William    schedule 10.06.2016
comment
@ Уильям Да, ты прав. Но это не так. Все обработчики событий и плакаты здесь должны работать в потоке пользовательского интерфейса. Даже это не означает, что они могут влиять друг на друга. Дело в том, что в процесс подписчика не должны вмешиваться никакие другие подписчики.   -  person neokim    schedule 14.06.2016


Ответы (1)


Вы правы.

Если вы посмотрите на исходный код Event Bus, вы увидите, что для тех же потоков (MAIN в вашем случае) Event Bus не проверяет, активна ли подписка во время процесса публикации:

сообщение()

Внутри этого метода шина событий достигает postSingleEventForEventType(), где получает все доступные на данный момент подписки и уведомляет их. Те, что из того же потока, выполняются без проверки:

EventBus#invokeSubscriber(subscription, event)

остальные запланированы и будут выполняться с помощью метода, который выполняет проверки:

EventBus#invokeSubscriber(pendingPost)

отменить регистрацию ()

Внутри этого метода Event Bus достигает unsubscribeByEventType(), где получает те же подписки, удаляет и деактивирует соответствующую:

List<Subscription> subscriptions = subscriptionsByEventType.get(eventClass);
for (...) {
    // ...
    subscription.active = false;
    subscriptions.remove(i);
}
person Artem Novikov    schedule 24.10.2016