EventBus: шта је то, принцип рада и магистрала догађаја Android

Аутор: IT Sectr Објављено: 2026-03-18 Време читања: 10 мин

EventBus је библиотека за Android која имплементира образац Publisher-Subscriber путем магистрале догађаја, омогућавајући размену података између компонената без директне зависности. Развијена од стране GreenRobot-а, библиотека поједностављује комуникацију између Activity, Fragment, Service и Background Thread-а. Према подацима GitHub-а (2025), EventBus има више од 25 хиљада звездица и користи се у хиљадама Android апликација. Основне операције су subscribe (претплата на догађај), post (слање догађаја) и sticky event (одложени догађај за нове претплатнике).

Главне тачке

  • EventBus — библиотека магистрале догађаја за слабо повезану комуникацију у Android-у.
  • @Subscribe — анотација која означава метод као руковалац догађајем одређеног типа.
  • EventBus.getDefault().post() шаље догађај свим претплаћеним руковаоцима.
  • Sticky event чува последњи догађај за доставу новим претплатницима.
  • ThreadMode одређује нит извршавања руковаоца: MAIN, POSTING, BACKGROUND, ASYNC.

Шта је EventBus?

EventBus је библиотека магистрале догађаја за Android, која имплементира образац Publisher-Subscriber (издавач-претплатник). Омогућава пренос догађаја између компоненти апликације (Activity, Fragment, Service, ViewModel) без стварања експлицитних зависности између њих. За разлику од стандардних Android механизама (Intent, BroadcastReceiver), EventBus ради унутар процеса и не користи IPC. Библиотека је оптимизована за перформансе и не користи рефлексију када је Subscriber Index правилно конфигурисан.

GreenRobot EventBus: архитектура

Архитектура EventBus-а се састоји од три кључна елемента: Event (POJO класа са подацима), Subscriber (објекат са методама означеним са @Subscribe) и EventBus (централни диспечер). Претплатник се региструје путем EventBus.getDefault().register(this), одјављивање — путем unregister(this). Догађаји су типизовани: руковаоци се претплаћују на одређену класу догађаја и позивају се само при post-у догађаја те класе или њених подкласа.

kotlin
// 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"))

Subscriber Index за перформансе

Подразумевано, EventBus користи рефлексију за проналажење @Subscribe метода при register(). Subscriber Index генерише индекс руковалаца у фази компилације путем annotation processor-а. Ово елиминише overhead рефлексије и убрзава регистрацију. За укључивање, додајте eventbus-annotation-processor у build.gradle. EventBus аутоматски користи индекс ако је доступан у classpath-у. Без индекса библиотека и даље ради, али са благим падом перформанси.

Како ради EventBus у Android-у?

При позиву EventBus.getDefault().post(event), библиотека одређује тип догађаја, проналази све регистроване претплатнике са @Subscribe методима који прихватају овај тип и позива их према наведеном ThreadMode-у. Претрага претплатника се врши по мапи Class → CopyOnWriteArrayList, изграђеној при регистрацији. Ако догађај нема претплатника, post се завршава без грешке — ово је safe-fail понашање.

Животни циклус регистрације

Претплатник треба да се региструје у onStart() и одјави у onStop(). Ако се региструје у onCreate() и одјави у onDestroy(), Activity уништена без позива onDestroy (због finish()) може остати на листи претплатника. Цурење претплатника — један од главних проблема EventBus-а: Activity која је остала на листи претплатника неће бити сакупљена од стране GC-ја док се не одјави. Увек упарујте register/unregister у одговарајућим lifecycle методама.

Приоритет руковалаца

Анотација @Subscribe подржава параметар priority (цео број, подразумевано 0). Руковаоци са вишим приоритетом се позивају раније. cancelEventDelivery() омогућава прекид испоруке догађаја осталим претплатницима. Ово је корисно за приоритетне руковаоце (логирање, аутентификација) који могу отказати обраду догађаја од стране нижих претплатника. Функција је доступна само у нити слања догађаја.

kotlin
// Сложени пример са приоритетом
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))

EventBus vs LocalBroadcastManager vs LiveData

Android нуди неколико механизама за унутарпроцесну комуникацију: EventBus, LocalBroadcastManager (застарео) и LiveData/Flow. Сваки има своје предности и недостатке. Избор зависи од архитектонског приступа и захтева за перформансама. Модерне препоруке Google-а нагињу ка LiveData и Flow због интеграције са Lifecycle-ом и одсуства цурења.

КарактеристикаEventBusLocalBroadcastManagerLiveData / Flow
ТипизацијаПутем класе догађајаПутем Intent filter-а (String)Путем generic типа
Lifecycle-awareНе (ручно одјављивање)Не (ручно одјављивање)Да (аутоматски)
StickyДа (postSticky)НеДа (LiveData — увек sticky)
ThreadModeMAIN, POSTING, BACKGROUND, ASYNCСамо mainПутем observe/observeOn
ПерформансеВисоке (Subscriber Index)Средње (IPC омотач)Високе (посматрање)

Када је EventBus бољи избор

EventBus је користан у пројектима са наслеђеним кодом (legacy code) и где LiveData/Flow нису доступни (Java-only пројекти). Sticky events EventBus-а пружају флексибилност која недостаје у LocalBroadcastManager-у. EventBus је такође једноставнији за слање догађаја из Service-а у Activity без ViewModel-а — посебно када треба обавестити о напретку позадинског задатка. Библиотека има минималну величину (око 50 KB) и не додаје зависности.

Када су LiveData/Flow бољи избор

LiveData и Flow су део Android Jetpack-а и интегрисани су са Lifecycle-ом. Аутоматски се одјављују при уништењу компоненте, елиминишући цурење меморије. Flow подржава корутине и сложене операторе трансформације. Google препоручује LiveData за UI слој и Flow за репозиторијуме. EventBus остаје за међумодулне догађаје, где навигација и пословна логика не одговарају MVVM-у.

Subscribe и Post: основне операције

Subscribe — регистрација руковаоца догађаја путем анотације @Subscribe. Метод мора бити public, void и прихватати тачно један параметар — тип догађаја. Post — слање догађаја свим претплаћеним руковаоцима путем EventBus.getDefault().post(event). Метод post не враћа резултат и не обавештава колико је руковаоца позвано. За догађаје са одговором користите посебну Event класу са пољем за резултат.

Креирање прилагођених догађаја

Догађај — било која Java/Kotlin класа. Препоручује се коришћење data class за непроменљиве догађаје и обичне класе за догађаје са mutable пољима. Именовање догађаја треба да одражава радњу: UserLoggedInEvent, DataLoadedEvent, NetworkErrorEvent. Избегавајте једну заједничку Event класу са пољем String type — то лишава предности типизације. Хијерархија догађаја (родитељски Event) омогућава претплату на групу сродних догађаја.

kotlin
// Хијерархија догађаја
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 Events и ThreadMode

Sticky event — догађај који се чува у EventBus-у након слања. Нови претплатници регистровани након postSticky() одмах добијају последњи sticky-догађај одговарајућег типа. Ово је згодно за пренос почетног стања: при отварању екрана, он добија последње податке послате пре његове регистрације. Брисање sticky-догађаја се врши путем EventBus.getDefault().removeStickyEvent(Class).

ThreadMode: четири режима извршавања

ThreadMode одређује у којој нити се позива руковалац. POSTING (подразумевано) — руковалац се извршава у истој нити у којој је позван post. MAIN — руковалац се извршава у main нити путем Handler-а. BACKGROUND — руковалац се извршава у background нити; ако је post позван у main нити, EventBus ставља руковаоца у ред background нити. ASYNC — сваки руковалац се извршава у посебној background нити из pool-а нити. За ажурирања UI-ја користите MAIN.

kotlin
// 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)

ThreadMode.BACKGROUND vs ASYNC

BACKGROUND користи једну background нит за све руковаоце — они се извршавају секвенцијално. ASYNC креира нову нит из pool-а за сваког руковаоца — они се извршавају паралелно. BACKGROUND је погодан за улазно-излазне операције са заједничком базом података. ASYNC — за независне дуге операције (мрежни захтеви). Оба режима захтевају thread-safe приступ заједничким ресурсима. Број нити: ASYNC pool је неограничен.

Типичне грешке и перформансе EventBus-а

При коришћењу EventBus-а, програмери често праве грешке које воде ка цурењу меморије, неочекиваним позивима и паду перформанси. Најкритичније: заборављено одјављивање у Activity, регистрација у onCreate (уместо onStart/onStop), претплата на Object (сви догађаји), слање догађаја у бесконачној петљи. Профилисање путем Android Profiler-а помаже у идентификовању проблема.

Цурење меморије кроз EventBus

Најчешћа грешка — регистрација Activity у onCreate() без одјаве у onDestroy(). Резултат: EventBus чува референцу на Activity, GC је не може ослободити. При ротацији екрана ствара се нова Activity, претходна остаје у меморији. Решење: увек упарујте register/unregister у onStart/onStop. За Fragment користите исти образац. Ако Activity задржава EventBus након finish-а, проверите путем Memory Profiler-а.

Перформансе: Subscriber Index

Без Subscriber Index-а, EventBus користи рефлексију за проналажење @Subscribe метода при сваком register(). На уређајима са Android 6-7, рефлексија ради споро, изазивајући кашњења до 50 ms. Subscriber Index у потпуности елиминише рефлексију: методи се индексирају у фази компилације путем annotation processor-а. За пројекте са 20+ претплатника индекс је обавезан. Проверите да су kapt или annotationProcessor повезани у build.gradle.

groovy
// 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')
    }
}

Алтернативе EventBus-у у савременом Android-у

Савремени пројекти на Kotlin и Jetpack Compose-у преферирају SharedFlow и Channel из библиотеке kotlinx.coroutines. SharedFlow подржава replay (sticky), buffering и backpressure. Channel — једнократне догађаје (toast, навигација). Оба решења су интегрисана са Lifecycle-ом путем repeatOnLifecycle и не захтевају ручно одјављивање. За нове пројекте препоручује се SharedFlow уместо EventBus-а. За постојеће пројекте миграција је оправдана при рефакторисању.

Често постављана питања

Која је разлика између EventBus-а и LiveData?

EventBus је магистрала догађаја за размену података између било којих компонената (Activity, Fragment, Service). LiveData је lifecycle-aware омотач за податке које посматра UI компонента. LiveData аутоматски управља претплатом путем Lifecycle-а. EventBus захтева ручно register/unregister. LiveData се препоручује за UI слој, EventBus — за међумодулну комуникацију где LiveData није згодан.

Шта је sticky event?

Sticky event — догађај који се чува у EventBus-у након слања. Нови претплатници регистровани након postSticky() одмах добијају последњи sticky-догађај. Користи се за почетно стање: при отварању екрана, он добија последње податке без поновног захтева. Брише се путем removeStickyEvent() или при слању новог sticky-догађаја истог типа.

Да ли је EventBus thread-safe?

Да, EventBus је thread-safe. Позив post() је могућ из било које нити. Испорука догађаја претплатницима се синхронизује унутар библиотеке. ThreadMode одређује нит извршавања руковаоца: MAIN (main нит путем Handler-а), POSTING (нит пошиљаоца), BACKGROUND (ред позадинских задатака), ASYNC (посебна нит). За ажурирања UI-ја користите MAIN, за тешке операције — ASYNC.

Како отклонити грешке у EventBus-у?

Укључите логирање путем EventBus.builder().logNoSubscriberMessages(true).sendNoSubscriberEvent(true).install(). Претплатите се на NoSubscriberEvent за праћење догађаја без руковалаца. Користите SubscriberExceptionEvent за глобално управљање изузецима. Android Profiler помаже у проналажењу цурења. За сложене сценарије напишите тест: EventBus.getDefault().register(mock) + post(event) + verify(mock).

Може ли се користити EventBus у Kotlin Multiplatform?

Не, EventBus (GreenRobot) је везан за Android SDK и JVM. За Kotlin Multiplatform користите Kotlin Multiplatform SharedFlow или KMMBus — библиотеке које подржавају заједнички код. EventBus на Android страни KMM пројекта ради, али није доступан у commonMain. За cross-platform догађаје пожељнији су изворни механизми платформе или апстракција путем expect/actual.

Резиме

  • EventBus — Publisher-Subscriber библиотека за Android, која имплементира магистралу догађаја са типизацијом путем POJO класа.
  • Анотација @Subscribe са параметрима threadMode, sticky, priority одређује понашање руковаоца догађаја.
  • post() шаље догађај свим претплатницима синхроно; postSticky() чува догађај за нове претплатнике.
  • ThreadMode управља нити извршавања: POSTING (нит пошиљаоца), MAIN (UI), BACKGROUND (ред), ASYNC (pool).
  • Subscriber Index annotation processor-а елиминише рефлексију и убрзава регистрацију.
  • Цурење меморије се спречава упаривањем register/unregister у onStart/onStop Activity или Fragment-а.
  • За нове пројекте пожељнији су SharedFlow/Channel из kotlinx.coroutines-а — lifecycle-aware су и thread-safe.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође