EventBus је библиотека за Android која имплементира образац Publisher-Subscriber путем магистрале догађаја, омогућавајући размену података између компонената без директне зависности. Развијена од стране GreenRobot-а, библиотека поједностављује комуникацију између Activity, Fragment, Service и Background Thread-а. Према подацима GitHub-а (2025), EventBus има више од 25 хиљада звездица и користи се у хиљадама Android апликација. Основне операције су subscribe (претплата на догађај), post (слање догађаја) и sticky event (одложени догађај за нове претплатнике).
Главне тачке
EventBus је библиотека магистрале догађаја за Android, која имплементира образац Publisher-Subscriber (издавач-претплатник). Омогућава пренос догађаја између компоненти апликације (Activity, Fragment, Service, ViewModel) без стварања експлицитних зависности између њих. За разлику од стандардних Android механизама (Intent, BroadcastReceiver), EventBus ради унутар процеса и не користи IPC. Библиотека је оптимизована за перформансе и не користи рефлексију када је Subscriber Index правилно конфигурисан.
Архитектура EventBus-а се састоји од три кључна елемента: Event (POJO класа са подацима), Subscriber (објекат са методама означеним са @Subscribe) и EventBus (централни диспечер). Претплатник се региструје путем EventBus.getDefault().register(this), одјављивање — путем unregister(this). Догађаји су типизовани: руковаоци се претплаћују на одређену класу догађаја и позивају се само при post-у догађаја те класе или њених подкласа.
// POJO догађај
data class MessageEvent(
val message: String,
val timestamp: Long = System.currentTimeMillis()
)
// Претплатник у 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
}
}
// Слање догађаја из друге компоненте
EventBus.getDefault().post(MessageEvent("Hello from Service"))
Подразумевано, EventBus користи рефлексију за проналажење @Subscribe метода при register(). Subscriber Index генерише индекс руковалаца у фази компилације путем annotation processor-а. Ово елиминише overhead рефлексије и убрзава регистрацију. За укључивање, додајте eventbus-annotation-processor у build.gradle. EventBus аутоматски користи индекс ако је доступан у classpath-у. Без индекса библиотека и даље ради, али са благим падом перформанси.
При позиву EventBus.getDefault().post(event), библиотека одређује тип догађаја, проналази све регистроване претплатнике са @Subscribe методима који прихватају овај тип и позива их према наведеном ThreadMode-у. Претрага претплатника се врши по мапи Class → CopyOnWriteArrayList
Претплатник треба да се региструје у onStart() и одјави у onStop(). Ако се региструје у onCreate() и одјави у onDestroy(), Activity уништена без позива onDestroy (због finish()) може остати на листи претплатника. Цурење претплатника — један од главних проблема EventBus-а: Activity која је остала на листи претплатника неће бити сакупљена од стране GC-ја док се не одјави. Увек упарујте register/unregister у одговарајућим lifecycle методама.
Анотација @Subscribe подржава параметар priority (цео број, подразумевано 0). Руковаоци са вишим приоритетом се позивају раније. cancelEventDelivery() омогућава прекид испоруке догађаја осталим претплатницима. Ово је корисно за приоритетне руковаоце (логирање, аутентификација) који могу отказати обраду догађаја од стране нижих претплатника. Функција је доступна само у нити слања догађаја.
// Сложени пример са приоритетом
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)
}
}
// Слање догађаја
EventBus.getDefault().post(NavigationEvent("profile", bundle))
Android нуди неколико механизама за унутарпроцесну комуникацију: EventBus, LocalBroadcastManager (застарео) и LiveData/Flow. Сваки има своје предности и недостатке. Избор зависи од архитектонског приступа и захтева за перформансама. Модерне препоруке Google-а нагињу ка LiveData и Flow због интеграције са Lifecycle-ом и одсуства цурења.
| Карактеристика | EventBus | LocalBroadcastManager | LiveData / Flow |
|---|---|---|---|
| Типизација | Путем класе догађаја | Путем Intent filter-а (String) | Путем generic типа |
| Lifecycle-aware | Не (ручно одјављивање) | Не (ручно одјављивање) | Да (аутоматски) |
| Sticky | Да (postSticky) | Не | Да (LiveData — увек sticky) |
| ThreadMode | MAIN, POSTING, BACKGROUND, ASYNC | Само main | Путем observe/observeOn |
| Перформансе | Високе (Subscriber Index) | Средње (IPC омотач) | Високе (посматрање) |
EventBus је користан у пројектима са наслеђеним кодом (legacy code) и где LiveData/Flow нису доступни (Java-only пројекти). Sticky events EventBus-а пружају флексибилност која недостаје у LocalBroadcastManager-у. EventBus је такође једноставнији за слање догађаја из Service-а у Activity без ViewModel-а — посебно када треба обавестити о напретку позадинског задатка. Библиотека има минималну величину (око 50 KB) и не додаје зависности.
LiveData и Flow су део Android Jetpack-а и интегрисани су са Lifecycle-ом. Аутоматски се одјављују при уништењу компоненте, елиминишући цурење меморије. Flow подржава корутине и сложене операторе трансформације. Google препоручује LiveData за UI слој и Flow за репозиторијуме. EventBus остаје за међумодулне догађаје, где навигација и пословна логика не одговарају MVVM-у.
Subscribe — регистрација руковаоца догађаја путем анотације @Subscribe. Метод мора бити public, void и прихватати тачно један параметар — тип догађаја. Post — слање догађаја свим претплаћеним руковаоцима путем EventBus.getDefault().post(event). Метод post не враћа резултат и не обавештава колико је руковаоца позвано. За догађаје са одговором користите посебну Event класу са пољем за резултат.
Догађај — било која Java/Kotlin класа. Препоручује се коришћење data class за непроменљиве догађаје и обичне класе за догађаје са mutable пољима. Именовање догађаја треба да одражава радњу: UserLoggedInEvent, DataLoadedEvent, NetworkErrorEvent. Избегавајте једну заједничку Event класу са пољем String type — то лишава предности типизације. Хијерархија догађаја (родитељски Event) омогућава претплату на групу сродних догађаја.
// Хијерархија догађаја
open class UserEvent
data class UserLoggedIn(val userId: String) : UserEvent()
data class UserLoggedOut(val reason: String) : UserEvent()
// Претплата на базну класу
class SessionManager {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onUserEvent(event: UserEvent) {
when (event) {
is UserLoggedIn -> startSession(event.userId)
is UserLoggedOut -> endSession(event.reason)
}
}
}
// Слање
EventBus.getDefault().post(UserLoggedIn("user_123"))
Позив EventBus.getDefault().register(this) скенира класу претплатника путем рефлексије или Subscriber Index-а и чува пронађене @Subscribe методе у мапи догађаја. Unregister уклања претплатника из мапе. Поновна регистрација без одјаве — грешка (појавиће се MultipleSubscriberException). За Fragment се региструјте у onStart() и одјавите у onStop(). За Service — у onCreate() и onDestroy(). За ViewModel се не препоручује — користите LiveData.
Sticky event — догађај који се чува у EventBus-у након слања. Нови претплатници регистровани након postSticky() одмах добијају последњи sticky-догађај одговарајућег типа. Ово је згодно за пренос почетног стања: при отварању екрана, он добија последње податке послате пре његове регистрације. Брисање sticky-догађаја се врши путем EventBus.getDefault().removeStickyEvent(Class).
ThreadMode одређује у којој нити се позива руковалац. POSTING (подразумевано) — руковалац се извршава у истој нити у којој је позван post. MAIN — руковалац се извршава у main нити путем Handler-а. BACKGROUND — руковалац се извршава у background нити; ако је post позван у main нити, EventBus ставља руковаоца у ред background нити. ASYNC — сваки руковалац се извршава у посебној background нити из pool-а нити. За ажурирања UI-ја користите MAIN.
// Sticky Event
data class LocationEvent(val lat: Double, val lng: Double)
// Слање sticky-догађаја из LocationService-а
EventBus.getDefault().postSticky(LocationEvent(55.7558, 37.6173))
// Претплатник добија последњу локацију одмах након регистрације
class MapFragment : Fragment() {
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
// Одмах ће добити LocationEvent ако је било 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)
}
}
// Брисање sticky-догађаја
EventBus.getDefault().removeStickyEvent(LocationEvent::class.java)
BACKGROUND користи једну background нит за све руковаоце — они се извршавају секвенцијално. ASYNC креира нову нит из pool-а за сваког руковаоца — они се извршавају паралелно. BACKGROUND је погодан за улазно-излазне операције са заједничком базом података. ASYNC — за независне дуге операције (мрежни захтеви). Оба режима захтевају thread-safe приступ заједничким ресурсима. Број нити: ASYNC pool је неограничен.
При коришћењу EventBus-а, програмери често праве грешке које воде ка цурењу меморије, неочекиваним позивима и паду перформанси. Најкритичније: заборављено одјављивање у Activity, регистрација у onCreate (уместо onStart/onStop), претплата на Object (сви догађаји), слање догађаја у бесконачној петљи. Профилисање путем Android Profiler-а помаже у идентификовању проблема.
Најчешћа грешка — регистрација Activity у onCreate() без одјаве у onDestroy(). Резултат: EventBus чува референцу на Activity, GC је не може ослободити. При ротацији екрана ствара се нова Activity, претходна остаје у меморији. Решење: увек упарујте register/unregister у onStart/onStop. За Fragment користите исти образац. Ако Activity задржава EventBus након finish-а, проверите путем Memory Profiler-а.
Без Subscriber Index-а, EventBus користи рефлексију за проналажење @Subscribe метода при сваком register(). На уређајима са Android 6-7, рефлексија ради споро, изазивајући кашњења до 50 ms. Subscriber Index у потпуности елиминише рефлексију: методи се индексирају у фази компилације путем annotation processor-а. За пројекте са 20+ претплатника индекс је обавезан. Проверите да су kapt или annotationProcessor повезани у build.gradle.
// build.gradle (app) — повезивање Subscriber Index-а
dependencies {
implementation 'org.greenrobot:eventbus:3.3.1'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.3.1'
}
// За Kotlin користите kapt
plugins {
id 'kotlin-kapt'
}
dependencies {
implementation 'org.greenrobot:eventbus:3.3.1'
kapt 'org.greenrobot:eventbus-annotation-processor:3.3.1'
}
// Конфигурација индекса (у defaultConfig-у)
kapt {
arguments {
arg('eventBusIndex', 'com.app.EventBusIndex')
}
}
Савремени пројекти на Kotlin и Jetpack Compose-у преферирају SharedFlow и Channel из библиотеке kotlinx.coroutines. SharedFlow подржава replay (sticky), buffering и backpressure. Channel — једнократне догађаје (toast, навигација). Оба решења су интегрисана са Lifecycle-ом путем repeatOnLifecycle и не захтевају ручно одјављивање. За нове пројекте препоручује се SharedFlow уместо EventBus-а. За постојеће пројекте миграција је оправдана при рефакторисању.
Често постављана питања
EventBus је магистрала догађаја за размену података између било којих компонената (Activity, Fragment, Service). LiveData је lifecycle-aware омотач за податке које посматра UI компонента. LiveData аутоматски управља претплатом путем Lifecycle-а. EventBus захтева ручно register/unregister. LiveData се препоручује за UI слој, EventBus — за међумодулну комуникацију где LiveData није згодан.
Sticky event — догађај који се чува у EventBus-у након слања. Нови претплатници регистровани након postSticky() одмах добијају последњи sticky-догађај. Користи се за почетно стање: при отварању екрана, он добија последње податке без поновног захтева. Брише се путем removeStickyEvent() или при слању новог sticky-догађаја истог типа.
Да, EventBus је thread-safe. Позив post() је могућ из било које нити. Испорука догађаја претплатницима се синхронизује унутар библиотеке. ThreadMode одређује нит извршавања руковаоца: MAIN (main нит путем Handler-а), POSTING (нит пошиљаоца), BACKGROUND (ред позадинских задатака), ASYNC (посебна нит). За ажурирања UI-ја користите MAIN, за тешке операције — ASYNC.
Укључите логирање путем EventBus.builder().logNoSubscriberMessages(true).sendNoSubscriberEvent(true).install(). Претплатите се на NoSubscriberEvent за праћење догађаја без руковалаца. Користите SubscriberExceptionEvent за глобално управљање изузецима. Android Profiler помаже у проналажењу цурења. За сложене сценарије напишите тест: EventBus.getDefault().register(mock) + post(event) + verify(mock).
Не, EventBus (GreenRobot) је везан за Android SDK и JVM. За Kotlin Multiplatform користите Kotlin Multiplatform SharedFlow или KMMBus — библиотеке које подржавају заједнички код. EventBus на Android страни KMM пројекта ради, али није доступан у commonMain. За cross-platform догађаје пожељнији су изворни механизми платформе или апстракција путем expect/actual.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође