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 генерирует индекс обработчиков на этапе компиляции через аннотационный процессор. Это устраняет накладные расходы рефлексии и ускоряет регистрацию. Для включения добавьте 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 (deprecated) и 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 КБ) и не добавляет зависимостей.

Когда 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 для неизменяемых событий и обычный 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 thread через Handler. BACKGROUND — обработчик выполняется в background thread; если post вызван в main thread, EventBus ставит обработчик в очередь background thread. ASYNC — каждый обработчик выполняется в отдельном background thread из пула потоков. Для 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 создаёт новый поток из пула для каждого обработчика — они выполняются параллельно. BACKGROUND подходит для операций ввода-вывода с общей базой данных. ASYNC — для независимых долгих операций (сетевые запросы). Оба режима требуют thread-safe доступа к общим ресурсам. Рассчитывайте количество потоков: пул ASYNC неограничен.

Типовые ошибки и производительность EventBus

При использовании EventBus разработчики часто допускают ошибки, ведущие к утечкам памяти, неожиданным вызовам и падению производительности. Наиболее критичные: забытая отписка в Activity, регистрация в onCreate (а не onStart/onStop), подписка на Object (все события), отправка событий в бесконечном цикле. Профилирование через Android Profiler помогает выявить проблемы.

Утечки памяти через EventBus

Самая распространённая ошибка — регистрация Activity в onCreate() без отписки в onDestroy(). Result: 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 мс. 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 потокобезопасен?

Да, EventBus потокобезопасен. Вызов post() возможен из любого потока. Доставка события подписчикам синхронизируется внутри библиотеки. ThreadMode определяет поток выполнения обработчика: MAIN (main thread через 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 (пул).
  • Subscriber Index аннотационного процессора устраняет рефлексию и ускоряет регистрацию.
  • Утечки памяти предотвращаются парным register/unregister в onStart/onStop Activity или Fragment.
  • Для новых проектов предпочтительнее SharedFlow/Channel из kotlinx.coroutines — они lifecycle-aware и thread-safe.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также