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 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 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.
// 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"))
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.
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
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.
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.
// 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))
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ă | EventBus | LocalBroadcastManager | LiveData / Flow |
|---|---|---|---|
| Tipizare | Prin clasa evenimentului | Prin Intent filter (String) | Prin tip generic |
| Lifecycle-aware | Nu (dezabonare manuală) | Nu (dezabonare manuală) | Da (automat) |
| Sticky | Da (postSticky) | Nu | Da (LiveData — întotdeauna sticky) |
| ThreadMode | MAIN, POSTING, BACKGROUND, ASYNC | Doar main | Prin observe/observeOn |
| Performanță | Ridicată (Subscriber Index) | Medie (împachetare IPC) | Ridicată (observare) |
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.
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 — î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.
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.
// 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"))
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 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 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.
// 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)
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.
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.
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.
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.
// 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')
}
}
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
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.
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.
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.
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).
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
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.
Citiți și