EventBus: co to jest, zasada działania i magistrala zdarzeń Android

Autor: IT Sectr Opublikowano: 2026-03-18 Czas czytania: 10 min

EventBus to biblioteka dla Android, implementująca wzorzec Publisher-Subscriber przez magistralę zdarzeń, umożliwiającą wymianę danych między komponentami bez bezpośredniej zależności. Opracowana przez GreenRobot, biblioteka upraszcza komunikację między Activity, Fragment, Service i Background Thread. Według danych GitHub (2025), EventBus ma ponad 25 tysięcy gwiazdek i jest używany w tysiącach aplikacji Android. Główne operacje to subscribe (subskrypcja zdarzenia), post (wysyłanie zdarzenia) i sticky event (zdarzenie opóźnione dla nowych subskrybentów).

Najważniejsze

  • EventBus — biblioteka magistrali zdarzeń dla luźno powiązanej komunikacji w Android.
  • @Subscribe — adnotacja oznaczająca metodę jako obsługę zdarzenia określonego typu.
  • EventBus.getDefault().post() wysyła zdarzenie do wszystkich zapisanych obsług.
  • Sticky event przechowuje ostatnie zdarzenie do dostarczenia nowym subskrybentom.
  • ThreadMode określa wątek wykonania obsługi: MAIN, POSTING, BACKGROUND, ASYNC.

Co to jest EventBus?

EventBus to biblioteka magistrali zdarzeń dla Android, implementująca wzorzec Publisher-Subscriber (wydawca-subskrybent). Umożliwia przesyłanie zdarzeń między komponentami aplikacji (Activity, Fragment, Service, ViewModel) bez tworzenia jawnych zależności między nimi. W przeciwieństwie do standardowych mechanizmów Android (Intent, BroadcastReceiver), EventBus działa wewnątrz procesu i nie używa IPC. Biblioteka jest zoptymalizowana pod kątem wydajności i nie używa refleksji przy poprawnie skonfigurowanym Subscriber Index.

GreenRobot EventBus: architektura

Architektura EventBus składa się z trzech kluczowych elementów: Event (klasa POJO z danymi), Subscriber (obiekt z metodami oznaczonymi @Subscribe) i EventBus (centralny dyspozytor). Subskrybent rejestruje się przez EventBus.getDefault().register(this), wypisanie — przez unregister(this). Zdarzenia są typowane: obsługi subskrybują konkretną klasę zdarzenia i są wywoływane tylko przy post zdarzenia tej klasy lub jej podklas.

kotlin
// Zdarzenie POJO
data class MessageEvent(
    val message: String,
    val timestamp: Long = System.currentTimeMillis()
)

// Subskrybent w Activity
class MainActivity : AppCompatActivity() {

    override fun onStart() {
        super.onStart()
        EventBus.getDefault().register(this)
    }

    override fun onStop() {
        EventBus.getDefault().unregister(this)
        super.onStop()
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onMessageEvent(event: MessageEvent) {
        textView.text = event.message
    }
}

// Wysyłanie zdarzenia z innego komponentu
EventBus.getDefault().post(MessageEvent("Hello from Service"))

Subscriber Index dla wydajności

Domyślnie EventBus używa refleksji do wyszukiwania metod @Subscribe przy register(). Subscriber Index generuje indeks obsług na etapie kompilacji przez procesor adnotacji. Eliminuje to narzut refleksji i przyspiesza rejestrację. Aby włączyć, dodaj eventbus-annotation-processor w build.gradle. EventBus automatycznie używa indeksu, jeśli jest dostępny w classpath. Bez indeksu biblioteka nadal działa, ale z niewielkim spadkiem wydajności.

Jak działa EventBus w Android?

Przy wywołaniu EventBus.getDefault().post(event) biblioteka określa typ zdarzenia, znajduje wszystkich zarejestrowanych subskrybentów z metodami @Subscribe przyjmującymi ten typ i wywołuje je zgodnie z określonym ThreadMode. Wyszukiwanie subskrybentów odbywa się po mapie Class → CopyOnWriteArrayList, zbudowanej podczas rejestracji. Jeśli zdarzenie nie ma subskrybentów, post kończy się bez błędu — to safe-fail zachowanie.

Cykl życia rejestracji

Subskrybent powinien zarejestrować się w onStart() i wyrejestrować w onStop(). Jeśli zarejestruje się w onCreate() i wypisze w onDestroy(), Activity zniszczona bez wywołania onDestroy (z powodu finish()) może pozostać na liście subskrybentów. Wyciek subskrybenta — jeden z głównych problemów EventBus: Activity pozostające na liście subskrybentów nie zostanie zebrane przez GC, dopóki się nie wypisze. Zawsze łącz register/unregister w odpowiednich metodach lifecycle.

Priorytet obsług

Adnotacja @Subscribe obsługuje parametr priority (liczba całkowita, domyślnie 0). Obsługi z wyższym priorytetem są wywoływane wcześniej. cancelEventDelivery() umożliwia przerwanie dostarczania zdarzenia pozostałym subskrybentom. Jest to przydatne dla priorytetowych obsług (logowanie, uwierzytelnianie), które mogą anulować przetwarzanie zdarzenia przez niższe obsługi. Funkcja dostępna tylko w wątku wysyłania zdarzenia.

kotlin
// Złożony przykład z priorytetem
data class NavigationEvent(val screen: String, val data: Bundle)

class NavigationInterceptor {
    @Subscribe(priority = 10, threadMode = ThreadMode.POSTING)
    fun onNavigationEvent(event: NavigationEvent) {
        if (event.screen == "restricted" && !isAuthorized) {
            EventBus.getDefault().cancelEventDelivery(event)
        }
    }
}

class AnalyticsLogger {
    @Subscribe(priority = 5)
    fun logNavigation(event: NavigationEvent) {
        analytics.logScreen(event.screen)
    }
}

// Wysyłanie zdarzenia
EventBus.getDefault().post(NavigationEvent("profile", bundle))

EventBus vs LocalBroadcastManager vs LiveData

Android oferuje kilka mechanizmów do komunikacji wewnątrzprocesowej: EventBus, LocalBroadcastManager (przestarzały) i LiveData/Flow. Każdy ma swoje zalety i wady. Wybór zależy od podejścia architektonicznego i wymagań dotyczących wydajności. Nowoczesne zalecenia Google skłaniają się ku LiveData i Flow ze względu na integrację z Lifecycle i brak wycieków.

CechaEventBusLocalBroadcastManagerLiveData / Flow
TypowaniePrzez klasę zdarzeniaPrzez Intent filter (String)Przez typ generyczny
Lifecycle-awareNie (ręczne wypisanie)Nie (ręczne wypisanie)Tak (automatycznie)
StickyTak (postSticky)NieTak (LiveData — zawsze sticky)
ThreadModeMAIN, POSTING, BACKGROUND, ASYNCTylko mainPrzez observe/observeOn
WydajnośćWysoka (Subscriber Index)Średnia (opakowanie IPC)Wysoka (obserwacja)

Kiedy EventBus jest lepszy

EventBus jest przydatny w projektach z dużym dziedzictwem (legacy code) i gdzie LiveData/Flow są niedostępne (projekty Java-only). Sticky events EventBus dają elastyczność, której brakuje w LocalBroadcastManager. EventBus jest również prostszy do wysyłania zdarzeń z Service do Activity bez ViewModel — szczególnie gdy trzeba powiadomić o postępie zadania w tle. Biblioteka ma minimalny rozmiar (około 50 KB) i nie dodaje zależności.

Kiedy LiveData/Flow są lepsze

LiveData i Flow są częścią Android Jetpack i zintegrowane z Lifecycle. Automatycznie wypisują się przy zniszczeniu komponentu, eliminując wycieki pamięci. Flow obsługuje korutyny i złożone operatory transformacji. Google zaleca LiveData dla warstwy UI i Flow dla repozytoriów. EventBus pozostaje do zdarzeń cross-modułowych, gdzie nawigacja i logika biznesowa nie pasują do MVVM.

Subscribe i Post: podstawowe operacje

Subscribe — rejestracja obsługi zdarzenia przez adnotację @Subscribe. Metoda musi być public, void i przyjmować dokładnie jeden parametr — typ zdarzenia. Post — wysyłanie zdarzenia do wszystkich zapisanych obsług przez EventBus.getDefault().post(event). Metoda post nie zwraca wyniku i nie informuje, ile obsług zostało wywołanych. Dla zdarzeń z odpowiedzią użyj osobnej klasy Event z polem na wynik.

Tworzenie niestandardowych zdarzeń

Zdarzenie to dowolna klasa Java/Kotlin. Zaleca się używanie data class dla niezmiennych zdarzeń i zwykłej klasy dla zdarzeń z mutable-polami. Nazewnictwo zdarzeń powinno odzwierciedlać akcję: UserLoggedInEvent, DataLoadedEvent, NetworkErrorEvent. Unikaj jednej wspólnej klasy Event z polem String type — pozbawia to zalet typowania. Hierarchia zdarzeń (rodzicielski Event) umożliwia subskrypcję grupy pokrewnych zdarzeń.

kotlin
// Hierarchia zdarzeń
open class UserEvent
data class UserLoggedIn(val userId: String) : UserEvent()
data class UserLoggedOut(val reason: String) : UserEvent()

// Subskrypcja klasy bazowej
class SessionManager {
    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onUserEvent(event: UserEvent) {
        when (event) {
            is UserLoggedIn -> startSession(event.userId)
            is UserLoggedOut -> endSession(event.reason)
        }
    }
}

// Wysyłanie
EventBus.getDefault().post(UserLoggedIn("user_123"))

Rejestracja i wyrejestrowanie

Wywołanie EventBus.getDefault().register(this) skanuje klasę subskrybenta przez refleksję lub Subscriber Index i zapisuje znalezione metody @Subscribe w mapie zdarzeń. Unregister usuwa subskrybenta z mapy. Ponowna rejestracja bez wypisania — błąd (wystąpi MultipleSubscriberException). Dla Fragment rejestruj się w onStart() i wypisuj w onStop(). Dla Service — w onCreate() i onDestroy(). Dla ViewModel nie zaleca się — używaj LiveData.

Sticky Events i ThreadMode

Sticky event — zdarzenie, które jest przechowywane w EventBus po wysłaniu. Nowi subskrybenci zarejestrowani po postSticky() natychmiast otrzymują ostatnie sticky-zdarzenie odpowiedniego typu. Jest to wygodne do przekazywania stanu początkowego: przy otwarciu ekranu otrzymuje on ostatnie dane wysłane przed jego rejestracją. Usunąć sticky-zdarzenie można przez EventBus.getDefault().removeStickyEvent(Class).

ThreadMode: cztery tryby wykonania

ThreadMode określa, w którym wątku wywoływana jest obsługa. POSTING (domyślnie) — obsługa wykonuje się w tym samym wątku, w którym wywołano post. MAIN — obsługa wykonuje się w main thread przez Handler. BACKGROUND — obsługa wykonuje się w background thread; jeśli post został wywołany w main thread, EventBus umieszcza obsługę w kolejce background thread. ASYNC — każda obsługa wykonuje się w osobnym background thread z puli wątków. Do aktualizacji UI używaj MAIN.

kotlin
// Sticky Event
data class LocationEvent(val lat: Double, val lng: Double)

// Wysyłanie sticky-zdarzenia z LocationService
EventBus.getDefault().postSticky(LocationEvent(55.7558, 37.6173))

// Subskrybent otrzymuje ostatnią lokalizację natychmiast po rejestracji
class MapFragment : Fragment() {
    override fun onStart() {
        super.onStart()
        EventBus.getDefault().register(this)
        // Natychmiast otrzyma LocationEvent, jeśli było postSticky
    }

    override fun onStop() {
        EventBus.getDefault().unregister(this)
        super.onStop()
    }

    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
    fun onLocationEvent(event: LocationEvent) {
        moveMapTo(event.lat, event.lng)
    }
}

// Usuwanie sticky-zdarzenia
EventBus.getDefault().removeStickyEvent(LocationEvent::class.java)

ThreadMode.BACKGROUND vs ASYNC

BACKGROUND używa jednego background-wątku dla wszystkich obsług — wykonują się sekwencyjnie. ASYNC tworzy nowy wątek z puli dla każdej obsługi — wykonują się równolegle. BACKGROUND nadaje się do operacji wejścia-wyjścia ze współdzieloną bazą danych. ASYNC — do niezależnych długich operacji (żądania sieciowe). Oba tryby wymagają thread-safe dostępu do współdzielonych zasobów. Liczba wątków: pula ASYNC jest nieograniczona.

Typowe błędy i wydajność EventBus

Podczas używania EventBus programiści często popełniają błędy prowadzące do wycieków pamięci, nieoczekiwanych wywołań i spadku wydajności. Najbardziej krytyczne: zapomniane wypisanie w Activity, rejestracja w onCreate (a nie onStart/onStop), subskrypcja na Object (wszystkie zdarzenia), wysyłanie zdarzeń w nieskończonej pętli. Profilowanie przez Android Profiler pomaga zidentyfikować problemy.

Wycieki pamięci przez EventBus

Najczęstszy błąd — rejestracja Activity w onCreate() bez wypisania w onDestroy(). Skutek: EventBus przechowuje referencję do Activity, GC nie może jej zwolnić. Przy obrocie ekranu tworzone jest nowe Activity, poprzednie pozostaje w pamięci. Rozwiązanie: zawsze łącz register/unregister w onStart/onStop. Dla Fragment używaj tego samego schematu. Jeśli Activity jest utrzymywane przez EventBus po finish, sprawdź przez Memory Profiler.

Wydajność: Subscriber Index

Bez Subscriber Index EventBus używa refleksji do wyszukiwania metod @Subscribe przy każdym register(). Na urządzeniach z Android 6-7 refleksja działa wolno, powodując opóźnienia do 50 ms. Subscriber Index eliminuje refleksję całkowicie: metody są indeksowane na etapie kompilacji przez annotation processor. Dla projektów z 20+ subskrybentami indeks jest obowiązkowy. Sprawdź, czy kapt lub annotationProcessor jest podłączony w build.gradle.

groovy
// build.gradle (app) — podłączenie Subscriber Index
dependencies {
    implementation 'org.greenrobot:eventbus:3.3.1'
    annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.3.1'
}

// Dla Kotlin użyj kapt
plugins {
    id 'kotlin-kapt'
}

dependencies {
    implementation 'org.greenrobot:eventbus:3.3.1'
    kapt 'org.greenrobot:eventbus-annotation-processor:3.3.1'
}

// Konfiguracja indeksu (w defaultConfig)
kapt {
    arguments {
        arg('eventBusIndex', 'com.app.EventBusIndex')
    }
}

Alternatywy dla EventBus we współczesnym Android

Nowoczesne projekty na Kotlin i Jetpack Compose preferują SharedFlow i Channel z biblioteki kotlinx.coroutines. SharedFlow obsługuje replay (sticky), buffering i backpressure. Channel — zdarzenia jednorazowe (toast, nawigacja). Oba rozwiązania są zintegrowane z Lifecycle przez repeatOnLifecycle i nie wymagają ręcznego wypisywania. Dla nowych projektów zaleca się SharedFlow zamiast EventBus. Dla istniejących projektów migracja jest uzasadniona przy refaktoringu.

Często zadawane pytania

Jaka jest różnica między EventBus a LiveData?

EventBus to magistrala zdarzeń do wymiany danych między dowolnymi komponentami (Activity, Fragment, Service). LiveData to lifecycle-aware opakowanie dla danych obserwowanych przez komponent UI. LiveData automatycznie zarządza subskrypcją przez Lifecycle. EventBus wymaga ręcznego register/unregister. LiveData jest zalecane dla warstwy UI, EventBus — do komunikacji cross-modułowej, gdzie LiveData jest niewygodny.

Co to jest sticky event?

Sticky event — zdarzenie, które jest przechowywane w EventBus po wysłaniu. Nowi subskrybenci zarejestrowani po postSticky() natychmiast otrzymują ostatnie sticky-zdarzenie. Używane do stanu początkowego: przy otwarciu ekranu otrzymuje on ostatnie dane bez ponownego żądania. Usuwane przez removeStickyEvent() lub przy wysłaniu nowego sticky-zdarzenia tego samego typu.

Czy EventBus jest bezpieczny wątkowo?

Tak, EventBus jest bezpieczny wątkowo. Wywołanie post() jest możliwe z dowolnego wątku. Dostarczanie zdarzenia do subskrybentów jest synchronizowane wewnątrz biblioteki. ThreadMode określa wątek wykonania obsługi: MAIN (main thread przez Handler), POSTING (wątek nadawcy), BACKGROUND (kolejka zadań tła), ASYNC (osobny wątek). Do aktualizacji UI używaj MAIN, do ciężkich operacji — ASYNC.

Jak debugować EventBus?

Włącz logowanie przez EventBus.builder().logNoSubscriberMessages(true).sendNoSubscriberEvent(true).install(). Subskrybuj NoSubscriberEvent do śledzenia zdarzeń bez obsług. Użyj SubscriberExceptionEvent do globalnej obsługi wyjątków. Android Profiler pomaga znaleźć wycieki. Dla złożonych scenariuszy napisz test: EventBus.getDefault().register(mock) + post(event) + verify(mock).

Czy można używać EventBus w Kotlin Multiplatform?

Nie, EventBus (GreenRobot) jest związany z Android SDK i JVM. Dla Kotlin Multiplatform używaj Kotlin Multiplatform SharedFlow lub KMMBus — bibliotek obsługujących wspólny kod. EventBus po stronie Android w projekcie KMM działa, ale nie jest dostępny w commonMain. Dla zdarzeń cross-platform preferowane są natywne mechanizmy platformy lub abstrakcja przez expect/actual.

Podsumowanie

  • EventBus — biblioteka Publisher-Subscriber dla Android, implementująca magistralę zdarzeń z typowaniem przez klasy POJO.
  • @Subscribe adnotacja z parametrami threadMode, sticky, priority określa zachowanie obsługi zdarzenia.
  • post() wysyła zdarzenie do wszystkich subskrybentów synchronicznie; postSticky() przechowuje zdarzenie dla nowych subskrybentów.
  • ThreadMode zarządza wątkiem wykonania: POSTING (wątek nadawcy), MAIN (UI), BACKGROUND (kolejka), ASYNC (pula).
  • Subscriber Index procesora adnotacji eliminuje refleksję i przyspiesza rejestrację.
  • Wycieki pamięci są zapobiegane przez parowanie register/unregister w onStart/onStop Activity lub Fragment.
  • Dla nowych projektów preferowane są SharedFlow/Channel z kotlinx.coroutines — są lifecycle-aware i thread-safe.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również