EventBus: ce este, principiul de funcționare și magistrala de evenimente Android

Autor: IT Sectr Publicat: 2026-03-18 Timp de citire: 10 min

EventBus este o bibliotecă pentru Android care implementează modelul Publisher-Subscriber printr-o magistrală de evenimente, permițând schimbul de date între componente fără dependență directă. Dezvoltată de GreenRobot, biblioteca simplifică comunicarea între Activity, Fragment, Service și Background Thread. Conform datelor GitHub (2025), EventBus are peste 25 de mii de stele și este folosit în mii de aplicații Android. Operațiile principale sunt subscribe (abonare la eveniment), post (trimiterea evenimentului) și sticky event (eveniment întârziat pentru noii abonați).

Principalele puncte

  • EventBus — bibliotecă de magistrală de evenimente pentru comunicare slab cuplată în Android.
  • @Subscribe — adnotare care marchează o metodă ca handler de eveniment de un anumit tip.
  • EventBus.getDefault().post() trimite evenimentul tuturor handlerelor abonate.
  • Sticky event păstrează ultimul eveniment pentru livrare către noii abonați.
  • ThreadMode determină firul de execuție al handler-ului: MAIN, POSTING, BACKGROUND, ASYNC.

Ce este EventBus?

EventBus este o bibliotecă de magistrală de evenimente pentru Android, care implementează modelul Publisher-Subscriber (editor-abonat). Permite transmiterea evenimentelor între componentele aplicației (Activity, Fragment, Service, ViewModel) fără a crea dependențe explicite între ele. Spre deosebire de mecanismele standard Android (Intent, BroadcastReceiver), EventBus funcționează în interiorul procesului și nu folosește IPC. Biblioteca este optimizată pentru performanță și nu folosește reflecție atunci când Subscriber Index este configurat corect.

Arhitectura GreenRobot EventBus

Arhitectura EventBus constă din trei elemente cheie: Event (clasă POJO cu date), Subscriber (obiect cu metode marcate cu @Subscribe) și EventBus (dispecerul central). Abonatul se înregistrează prin EventBus.getDefault().register(this), dezabonarea — prin unregister(this). Evenimentele sunt tipizate: handlerele se abonează la o anumită clasă de eveniment și sunt apelate doar la postarea evenimentului acestei clase sau a subclaselor sale.

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

// Abonat în 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
    }
}

// Trimiterea evenimentului dintr-un alt component
EventBus.getDefault().post(MessageEvent("Hello from Service"))

Subscriber Index pentru performanță

Implicit, EventBus folosește reflecția pentru a găsi metodele @Subscribe la register(). Subscriber Index generează un index al handlerelor în faza de compilare printr-un annotation processor. Aceasta elimină overhead-ul reflecției și accelerează înregistrarea. Pentru activare, adăugați eventbus-annotation-processor în build.gradle. EventBus folosește automat indexul dacă este disponibil în classpath. Fără index, biblioteca funcționează în continuare, dar cu o ușoară scădere a performanței.

Cum funcționează EventBus în Android?

La apelul EventBus.getDefault().post(event), biblioteca determină tipul evenimentului, găsește toți abonații înregistrați cu metode @Subscribe care acceptă acest tip și îi apelează conform ThreadMode specificat. Căutarea abonaților se execută după harta Class → CopyOnWriteArrayList, construită la înregistrare. Dacă evenimentul nu are abonați, post se încheie fără eroare — acesta este comportamentul safe-fail.

Ciclul de viață al înregistrării

Abonatul trebuie să se înregistreze în onStart() și să se dezaboneze în onStop(). Dacă se înregistrează în onCreate() și se dezabonează în onDestroy(), Activity distrusă fără apelarea onDestroy (din cauza finish()) poate rămâne în lista de abonați. Scurgerea de abonat — una dintre principalele probleme ale EventBus: Activity rămasă în lista de abonați nu va fi colectată de GC până când nu se dezabonează. Împerecheați întotdeauna register/unregister în metodele lifecycle corecte.

Prioritatea handlerelor

Adnotarea @Subscribe suportă parametrul priority (număr întreg, implicit 0). Handlerele cu prioritate mai mare sunt apelate mai devreme. cancelEventDelivery() permite întreruperea livrării evenimentului către ceilalți abonați. Acest lucru este util pentru handlerele prioritare (logare, autentificare) care pot anula procesarea evenimentului de către abonații inferiori. Funcția este disponibilă doar în firul de trimitere a evenimentului.

kotlin
// Exemplu complex cu prioritate
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)
    }
}

// Trimiterea evenimentului
EventBus.getDefault().post(NavigationEvent("profile", bundle))

EventBus vs LocalBroadcastManager vs LiveData

Android oferă mai multe mecanisme pentru comunicarea intra-proces: EventBus, LocalBroadcastManager (depreciat) și LiveData/Flow. Fiecare are avantajele și dezavantajele sale. Alegerea depinde de abordarea arhitecturală și de cerințele de performanță. Recomandările moderne Google înclină spre LiveData și Flow datorită integrării cu Lifecycle și absenței scurgerilor.

CaracteristicăEventBusLocalBroadcastManagerLiveData / Flow
TipizarePrin clasa evenimentuluiPrin Intent filter (String)Prin tip generic
Lifecycle-awareNu (dezabonare manuală)Nu (dezabonare manuală)Da (automat)
StickyDa (postSticky)NuDa (LiveData — întotdeauna sticky)
ThreadModeMAIN, POSTING, BACKGROUND, ASYNCDoar mainPrin observe/observeOn
PerformanțăRidicată (Subscriber Index)Medie (împachetare IPC)Ridicată (observare)

Când EventBus este preferabil

EventBus este util în proiecte cu cod moștenit (legacy code) și acolo unde LiveData/Flow nu sunt disponibile (proiecte Java-only). Sticky events EventBus oferă flexibilitate care lipsește în LocalBroadcastManager. EventBus este, de asemenea, mai simplu pentru trimiterea evenimentelor din Service către Activity fără ViewModel — mai ales când trebuie să notificați despre progresul unei sarcini de fundal. Biblioteca are o dimensiune minimă (aproximativ 50 KB) și nu adaugă dependențe.

Când LiveData/Flow sunt preferabile

LiveData și Flow fac parte din Android Jetpack și sunt integrate cu Lifecycle. Se dezabonează automat la distrugerea componentului, eliminând scurgerile de memorie. Flow suportă corutine și operatori complexi de transformare. Google recomandă LiveData pentru stratul UI și Flow pentru depozite. EventBus rămâne pentru evenimente cross-modul, unde navigația și logica de afaceri nu se încadrează în MVVM.

Subscribe și Post: operații de bază

Subscribe — înregistrarea unui handler de eveniment prin adnotarea @Subscribe. Metoda trebuie să fie public, void și să accepte exact un parametru — tipul evenimentului. Post — trimiterea evenimentului către toate handlerele abonate prin EventBus.getDefault().post(event). Metoda post nu returnează rezultat și nu informează câte handlere au fost apelate. Pentru evenimente cu răspuns, utilizați o clasă Event separată cu un câmp pentru rezultat.

Crearea evenimentelor personalizate

Evenimentul — orice clasă Java/Kotlin. Se recomandă utilizarea data class pentru evenimente imuabile și a unei clase obișnuite pentru evenimente cu câmpuri mutable. Denumirea evenimentelor trebuie să reflecte acțiunea: UserLoggedInEvent, DataLoadedEvent, NetworkErrorEvent. Evitați o singură clasă Event comună cu un câmp String type — aceasta lipsește de avantajele tipizării. Ierarhia evenimentelor (Event părinte) permite abonarea la un grup de evenimente înrudite.

kotlin
// Ierarhia evenimentelor
open class UserEvent
data class UserLoggedIn(val userId: String) : UserEvent()
data class UserLoggedOut(val reason: String) : UserEvent()

// Abonare la clasa de bază
class SessionManager {
    @Subscribe(threadMode = ThreadMode.MAIN)
    fun onUserEvent(event: UserEvent) {
        when (event) {
            is UserLoggedIn -> startSession(event.userId)
            is UserLoggedOut -> endSession(event.reason)
        }
    }
}

// Trimitere
EventBus.getDefault().post(UserLoggedIn("user_123"))

Înregistrare și dezabonare

Apelul EventBus.getDefault().register(this) scanează clasa abonatului prin reflecție sau Subscriber Index și salvează metodele @Subscribe găsite în harta evenimentelor. Unregister șterge abonatul din hartă. Reînregistrarea fără dezabonare — eroare (va apărea MultipleSubscriberException). Pentru Fragment, înregistrați-vă în onStart() și dezabonați-vă în onStop(). Pentru Service — în onCreate() și onDestroy(). Pentru ViewModel nu se recomandă — folosiți LiveData.

Sticky Events și ThreadMode

Sticky event — un eveniment care este păstrat în EventBus după trimitere. Noii abonați înregistrați după postSticky() primesc imediat ultimul sticky-eveniment de tipul corespunzător. Acest lucru este convenabil pentru transmiterea stării inițiale: la deschiderea ecranului, acesta primește ultimele date trimise înainte de înregistrarea sa. Ștergerea sticky-evenimentului se poate face prin EventBus.getDefault().removeStickyEvent(Class).

ThreadMode: patru moduri de execuție

ThreadMode determină în ce fir este apelat handler-ul. POSTING (implicit) — handler-ul se execută în același fir în care a fost apelat post. MAIN — handler-ul se execută în main thread prin Handler. BACKGROUND — handler-ul se execută în background thread; dacă post a fost apelat în main thread, EventBus plasează handler-ul în coada background thread. ASYNC — fiecare handler se execută într-un background thread separat din pool-ul de fire. Pentru actualizări UI, folosiți MAIN.

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

// Trimiterea sticky-evenimentului din LocationService
EventBus.getDefault().postSticky(LocationEvent(55.7558, 37.6173))

// Abonatul primește ultima locație imediat după înregistrare
class MapFragment : Fragment() {
    override fun onStart() {
        super.onStart()
        EventBus.getDefault().register(this)
        // Va primi imediat LocationEvent dacă a fost 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)
    }
}

// Ștergerea sticky-evenimentului
EventBus.getDefault().removeStickyEvent(LocationEvent::class.java)

ThreadMode.BACKGROUND vs ASYNC

BACKGROUND folosește un singur background-thread pentru toate handlerele — acestea se execută secvențial. ASYNC creează un nou fir din pool pentru fiecare handler — acestea se execută în paralel. BACKGROUND este potrivit pentru operații de intrare-ieșire cu o bază de date partajată. ASYNC — pentru operații lungi independente (cereri de rețea). Ambele moduri necesită acces thread-safe la resursele partajate. Numărul de fire: pool-ul ASYNC este nelimitat.

Erori tipice și performanța EventBus

La utilizarea EventBus, dezvoltătorii fac adesea greșeli care duc la scurgeri de memorie, apeluri neașteptate și scăderea performanței. Cele mai critice: dezabonarea uitată în Activity, înregistrarea în onCreate (nu în onStart/onStop), abonarea la Object (toate evenimentele), trimiterea evenimentelor în buclă infinită. Profilarea prin Android Profiler ajută la identificarea problemelor.

Scurgeri de memorie prin EventBus

Cea mai frecventă greșeală — înregistrarea Activity în onCreate() fără dezabonare în onDestroy(). Rezultat: EventBus păstrează referința către Activity, GC nu o poate elibera. La rotirea ecranului se creează o nouă Activity, cea anterioară rămâne în memorie. Soluție: împerecheați întotdeauna register/unregister în onStart/onStop. Pentru Fragment, folosiți aceeași schemă. Dacă Activity este reținută de EventBus după finish, verificați prin Memory Profiler.

Performanță: Subscriber Index

Fără Subscriber Index, EventBus folosește reflecția pentru a găsi metodele @Subscribe la fiecare register(). Pe dispozitivele cu Android 6-7, reflecția funcționează lent, provocând întârzieri de până la 50 ms. Subscriber Index elimină complet reflecția: metodele sunt indexate în faza de compilare prin annotation processor. Pentru proiecte cu 20+ abonați, indexul este obligatoriu. Verificați că kapt sau annotationProcessor este conectat în build.gradle.

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

// Pentru Kotlin folosiți kapt
plugins {
    id 'kotlin-kapt'
}

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

// Configurarea indexului (în defaultConfig)
kapt {
    arguments {
        arg('eventBusIndex', 'com.app.EventBusIndex')
    }
}

Alternative la EventBus în Android modern

Proiectele moderne pe Kotlin și Jetpack Compose preferă SharedFlow și Channel din biblioteca kotlinx.coroutines. SharedFlow suportă replay (sticky), buffering și backpressure. Channel — evenimente unice (toast, navigație). Ambele soluții sunt integrate cu Lifecycle prin repeatOnLifecycle și nu necesită dezabonare manuală. Pentru proiecte noi, se recomandă SharedFlow în loc de EventBus. Pentru proiecte existente, migrarea este justificată la refactorizare.

Întrebări frecvente

Care este diferența dintre EventBus și LiveData?

EventBus este o magistrală de evenimente pentru schimbul de date între orice componente (Activity, Fragment, Service). LiveData este o împachetare lifecycle-aware pentru date observate de un component UI. LiveData gestionează automat abonarea prin Lifecycle. EventBus necesită register/unregister manual. LiveData este recomandat pentru stratul UI, EventBus — pentru comunicarea cross-modul, unde LiveData este incomod.

Ce este sticky event?

Sticky event — un eveniment care este păstrat în EventBus după trimitere. Noii abonați înregistrați după postSticky() primesc imediat ultimul sticky-eveniment. Folosit pentru starea inițială: la deschiderea ecranului, acesta primește ultimele date fără o cerere repetată. Se șterge prin removeStickyEvent() sau la trimiterea unui nou sticky-eveniment de același tip.

EventBus este thread-safe?

Da, EventBus este thread-safe. Apelul post() este posibil din orice fir. Livrarea evenimentului către abonați este sincronizată în interiorul bibliotecii. ThreadMode determină firul de execuție al handler-ului: MAIN (main thread prin Handler), POSTING (firul expeditorului), BACKGROUND (coada de sarcini de fundal), ASYNC (fir separat). Pentru actualizări UI, folosiți MAIN, pentru operații grele — ASYNC.

Cum să depanați EventBus?

Activați logarea prin EventBus.builder().logNoSubscriberMessages(true).sendNoSubscriberEvent(true).install(). Abonați-vă la NoSubscriberEvent pentru urmărirea evenimentelor fără handlere. Folosiți SubscriberExceptionEvent pentru gestionarea globală a excepțiilor. Android Profiler ajută la găsirea scurgerilor. Pentru scenarii complexe, scrieți un test: EventBus.getDefault().register(mock) + post(event) + verify(mock).

Se poate folosi EventBus în Kotlin Multiplatform?

Nu, EventBus (GreenRobot) este legat de Android SDK și JVM. Pentru Kotlin Multiplatform, folosiți Kotlin Multiplatform SharedFlow sau KMMBus — biblioteci care suportă codul comun. EventBus pe partea Android a proiectului KMM funcționează, dar nu este disponibil în commonMain. Pentru evenimente cross-platform, sunt preferate mecanismele native ale platformei sau abstractizarea prin expect/actual.

Rezumat

  • EventBus — bibliotecă Publisher-Subscriber pentru Android, care implementează o magistrală de evenimente cu tipizare prin clase POJO.
  • Adnotarea @Subscribe cu parametrii threadMode, sticky, priority determină comportamentul handler-ului de eveniment.
  • post() trimite evenimentul tuturor abonaților sincron; postSticky() păstrează evenimentul pentru noi abonați.
  • ThreadMode gestionează firul de execuție: POSTING (firul expeditorului), MAIN (UI), BACKGROUND (coadă), ASYNC (pool).
  • Subscriber Index al annotation processor-ului elimină reflecția și accelerează înregistrarea.
  • Scurgerile de memorie sunt prevenite prin împerecherea register/unregister în onStart/onStop ale Activity sau Fragment.
  • Pentru proiecte noi, SharedFlow/Channel din kotlinx.coroutines sunt preferabile — sunt lifecycle-aware și thread-safe.

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și