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 генерує індекс обробників на етапі компіляції через анотаційний процесор. Це усуває накладні витрати рефлексії та прискорює реєстрацію. Для включення додайте 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 КБ) та не додає залежностей.
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 для незмінних подій та звичайний 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 thread через Handler. BACKGROUND — обробник виконується в background thread; якщо post викликаний в main thread, EventBus ставить обробник в чергу background thread. ASYNC — кожен обробник виконується в окремому background thread з пулу потоків. Для 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 створює новий потік з пулу для кожного обробника — вони виконуються паралельно. BACKGROUND підходить для операцій введення-виведення з спільною базою даних. ASYNC — для незалежних довгих операцій (мережеві запити). Обидва режими вимагають thread-safe доступу до спільних ресурсів. Розраховуйте кількість потоків: пул ASYNC необмежений.
При використанні 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 мс. 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 потокобезпечний. Виклик post() можливий з будь-якого потоку. Доставка події підписникам синхронізується всередині бібліотеки. ThreadMode визначає потік виконання обробника: MAIN (main thread через 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. Для крос-платформних подій краще використовувати нативні механізми платформи або абстракцію через expect/actual.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також