Szyna zdarzeń
Szyna zdarzeń EventBus
upraszcza komunikacje pomiędzy różnymi komponentami systemu Android
(Activity
, Fragment
, Service
itp) za pomocą zdarzeń (event
) co szczególnie może być przydatne w zadaniach asynchronicznych. Jest realizacją wzorca Obserwator
(Observer
) - wprowadza podział na nadawców oraz odbiorców. Zdarzenie emitowane jest przez jednego nadawcę i trafia do wszystkich aktywnych odbiorców zarejestrowanych do szyny zdarzeń. Dobrze radzi sobie z cyklem życia komponentów oraz wątkami tła, posiada lekką strukturę, unika złożonych zależności i upraszcza kod. Jednakże z momentem wprowadzenia, stabilizacji i zyskiwania popularności programowania reaktywnego w RxJava
, architektura szyny zdarzeń staje się przestarzała. Oba projekty implementują ten sam model programowania oparty o zdarzenia lecz RxJava
jest bardziej wydajne i zapewnia lepszą kontrolę nad wątkami przez co wydaje się naturalnym następnikiem. Konfiguracja i zmiana domyślnego zachowania instancji EventBus
powinna zostać przeprowadzona przed pierwszym użyciem instancji szyny zdarzeń.
class App : Application() {
override fun onCreate() {
super.onCreate()
//customize default EventBus instance must be called before first subscriber called
EventBus.builder()
.logNoSubscriberMessages(false) //do not log when no subscriber exists
.sendNoSubscriberEvent(false) //do not send SubscriberEvent if no subscriber exists
.throwSubscriberException(BuildConfig.DEBUG) //throw exceptions only in debug mode
.installDefaultEventBus()
//or create custom instance by using build method
}
}
Rejestracja
Aby móc odbierać zdarzenia zarówno subskrybenci powinni dokonać rejestracji do instancji szyny zdarzeń EventBus zgodnie ze swoim cyklem życia. Należy również pamiętać o prawidłowym odrejestrowaniu komponentów co w przeciwnym razie może prowadzić do wycieku pamięci.
//some Android component class
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
//register in proper place of lifecycle
public override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
//unregister to avoid leak memory
public override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
}
}
Zdarzenie
Zdarzenie jest niczym innym jak emisją obiektu danej klasy przez nadawcę. W sytuacji, gdy pożądane jest rozróżnienie zdarzeń o tej samej strukturze należy stworzyć dla każdego rodzaju zdarzenia klasę opakowującą (wrapper
). Dobrą praktyką jest zawieranie w nazwie klasy słowa Event
.
data class MessageEvent(val number: Int, val text: String) {
//more fields
}
//if want emit some primitive type create class wrapper
//Custom1Event and Custom2Event have the same properties but are different events
class Custom1Event(val message: String) {
}
class Custom2Event(val name: String) {
}
Nadawca
Wysyłania zdarzenia zachodzi na instancji EventBus
z dowolnego fragmentu kodu i przeprowadzane jest przez przekazywanie obiektu do metody post
lub postSticky
(dla zdarzenia, które ma zostać zapamiętane do przyszłego użycia).
EventBus.getDefault().post(MessageEvent(100, "text"))
EventBus.getDefault().postSticky(Custom2Event("Johnnie"))
Subskrybent
Aby zarejestrowany komponent mógł reagować na emisję danego zdarzenia powinien implementować metodę oznaczoną adnotacją @Subscribe
i przyjmującą jako parametr wybrany typ (nazwa metody nie ma znaczenia). Dodatkowo istnieje możliwość ustawienia wątku roboczego na jakim odebrane zdarzenie będzie wykonywało pracę (threadMode
), ustalenie priorytetu subskrypcji w obrębie tego samego wątku (priority
) czy zdecydowanie o pobieraniu ostatniej znanej instancji zdarzenia (sticky
). Każdy subskrybent zostanie wywołane dokładnie raz.
//choose one of POSTING, MAIN, BACKGROUND, MAIN_ORDERED, ASYNC
@Subscribe(threadMode = ThreadMode.MAIN) //ThreadMode.POSTING is by default
fun onMessageEvent(event: MessageEvent) {
//do something with received event on the main UI thread
}
//priority 0 is default
@Subscribe
fun onMessageEvent(event: Custom1Event) {
//do something with received event on the background thread
//on some conditions
//prevent delivery to other subscribers with lower priority
EventBus.getDefault().cancelEventDelivery(event)
}
@Subscribe(priority = 1)
fun onMessageEventExtra(event: Custom1Event) {
//do something with received event on the background thread
}
//received value only from events posted by postSticky
@Subscribe(sticky = true)
fun onMessageEvent(event: Custom2Event) {
//receive previously posted sticky event immediately
//listen for new event
}