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 (deprecated) и 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(). Result: 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. Для cross-platform событий предпочтительнее нативные механизмы платформы или абстракция через expect/actual.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также