EventBus는 이벤트 버스를 통해 Publisher-Subscriber 패턴을 구현하는 Android 라이브러리로, 컴포넌트 간 직접적인 의존성 없이 데이터를 교환할 수 있습니다. GreenRobot이 개발한 이 라이브러리는 Activity, Fragment, Service 및 Background Thread 간의 통신을 단순화합니다. GitHub 데이터(2025년)에 따르면 EventBus는 25,000개 이상의 스타를 보유하고 있으며 수천 개의 Android 애플리케이션에서 사용됩니다. 주요 작업은 subscribe(이벤트 구독), post(이벤트 전송) 및 sticky event(새 구독자를 위한 지연 이벤트)입니다.
핵심 사항
EventBus는 Publisher-Subscriber 패턴을 구현하는 Android용 이벤트 버스 라이브러리입니다. 애플리케이션 컴포넌트(Activity, Fragment, Service, ViewModel) 간에 명시적 의존성을 만들지 않고 이벤트를 전달할 수 있습니다. 표준 Android 메커니즘(Intent, BroadcastReceiver)과 달리 EventBus는 프로세스 내에서 작동하며 IPC를 사용하지 않습니다. 라이브러리는 성능에 최적화되어 있으며 Subscriber Index가 올바르게 구성된 경우 리플렉션을 사용하지 않습니다.
EventBus 아키텍처는 세 가지 핵심 요소로 구성됩니다: Event(데이터가 있는 POJO 클래스), Subscriber(@Subscribe로 애노테이션된 메서드가 있는 객체), EventBus(중앙 디스패처). 구독자는 EventBus.getDefault().register(this)를 통해 등록하고 unregister(this)를 통해 등록을 취소합니다. 이벤트는 유형화됩니다: 핸들러는 특정 이벤트 클래스를 구독하며 해당 클래스 또는 하위 클래스의 이벤트가 게시된 경우에만 호출됩니다.
// 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는 register() 중에 @Subscribe 메서드를 찾기 위해 리플렉션을 사용합니다. Subscriber Index는 애노테이션 프로세서를 통해 컴파일 타임에 핸들러 인덱스를 생성합니다. 이는 리플렉션 오버헤드를 제거하고 등록을 가속화합니다. 활성화하려면 build.gradle에 eventbus-annotation-processor를 추가하세요. EventBus는 classpath에서 사용 가능한 경우 인덱스를 자동으로 사용합니다. 인덱스가 없어도 라이브러리는 계속 작동하지만 성능이 약간 저하됩니다.
EventBus.getDefault().post(event)가 호출되면 라이브러리는 이벤트 유형을 확인하고 해당 유형을 수락하는 @Subscribe 메서드가 있는 등록된 모든 구독자를 찾아 지정된 ThreadMode에 따라 호출합니다. 구독자 검색은 등록 중에 구축된 Class → CopyOnWriteArrayList
구독자는 onStart()에서 등록하고 onStop()에서 등록을 취소해야 합니다. onCreate()에서 등록하고 onDestroy()에서 취소하면 finish()로 인해 onDestroy가 호출되지 않고 소멸된 Activity가 구독자 목록에 남을 수 있습니다. 구독자 누수는 EventBus의 주요 문제 중 하나입니다: 구독자 목록에 남아 있는 Activity는 등록을 취소할 때까지 GC가 해제할 수 없습니다. 항상 올바른 수명 주기 메서드에서 register/unregister를 쌍으로 사용하세요.
@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의 최신 권장 사항은 Lifecycle 통합과 누수 부재로 인해 LiveData와 Flow를 선호합니다.
| 특징 | EventBus | LocalBroadcastManager | LiveData / Flow |
|---|---|---|---|
| 유형화 | 이벤트 클래스 통해 | Intent 필터(String) 통해 | 제네릭 유형 통해 |
| Lifecycle-aware | 아니요(수동 취소) | 아니요(수동 취소) | 예(자동) |
| Sticky | 예(postSticky) | 아니요 | 예(LiveData는 항상 sticky) |
| ThreadMode | MAIN, POSTING, BACKGROUND, ASYNC | main만 | observe/observeOn 통해 |
| 성능 | 높음(Subscriber Index) | 중간(IPC 래퍼) | 높음(관찰) |
EventBus는 레거시 코드가 많고 LiveData/Flow를 사용할 수 없는(Java 전용 프로젝트) 프로젝트에 유용합니다. EventBus의 sticky events는 LocalBroadcastManager에는 없는 유연성을 제공합니다. EventBus는 ViewModel 없이 Service에서 Activity로 이벤트를 보내는 데도 더 쉽습니다 — 특히 백그라운드 작업 진행 상황을 알려야 할 때 유용합니다. 라이브러리 크기는 최소(약 50KB)이며 종속성을 추가하지 않습니다.
LiveData와 Flow는 Android Jetpack의 일부이며 Lifecycle과 통합되어 있습니다. 컴포넌트가 소멸되면 자동으로 구독을 취소하여 메모리 누수를 제거합니다. Flow는 코루틴과 복잡한 변환 연산자를 지원합니다. Google은 UI 레이어에는 LiveData를, 리포지토리에는 Flow를 권장합니다. EventBus는 탐색 및 비즈니스 로직이 MVVM에 맞지 않는 모듈 간 이벤트에 여전히 유용합니다.
Subscribe는 @Subscribe 애노테이션을 통한 이벤트 핸들러 등록입니다. 메서드는 public, void여야 하며 정확히 하나의 매개변수(이벤트 유형)를 수락해야 합니다. Post는 EventBus.getDefault().post(event)를 통해 구독 중인 모든 핸들러에 이벤트를 전송하는 것입니다. post 메서드는 결과를 반환하지 않으며 호출된 핸들러 수를 표시하지 않습니다. 응답이 필요한 이벤트의 경우 결과 필드가 있는 별도의 Event 클래스를 사용하세요.
이벤트는 모든 Java/Kotlin 클래스입니다. 변경 불가능한 이벤트에는 data class를, 변경 가능한 필드가 있는 이벤트에는 일반 클래스를 사용하는 것이 좋습니다. 이벤트 이름은 작업을 반영해야 합니다: UserLoggedInEvent, DataLoadedEvent, NetworkErrorEvent. String 유형 필드가 있는 단일 일반 Event 클래스는 피하세요 — 유형화의 이점이 사라집니다. 이벤트 계층 구조(부모 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 event를 즉시 받습니다. 이는 초기 상태 전달에 편리합니다: 화면을 열면 등록 전에 전송된 최신 데이터를 받습니다. EventBus.getDefault().removeStickyEvent(Class)를 통해 sticky event를 제거할 수 있습니다.
ThreadMode는 핸들러가 실행되는 스레드를 결정합니다. POSTING(기본값) — 핸들러는 post가 호출된 동일한 스레드에서 실행됩니다. MAIN — 핸들러는 Handler를 통해 메인 스레드에서 실행됩니다. BACKGROUND — 핸들러는 백그라운드 스레드에서 실행됩니다. post가 메인 스레드에서 호출된 경우 EventBus는 핸들러를 백그라운드 스레드 큐에 넣습니다. ASYNC — 각 핸들러는 스레드 풀에서 별도의 백그라운드 스레드로 실행됩니다. UI 업데이트에는 MAIN을 사용하세요.
// Sticky Event
data class LocationEvent(val lat: Double, val lng: Double)
// LocationService에서 sticky 이벤트 전송
EventBus.getDefault().postSticky(LocationEvent(55.7558, 37.6173))
// 구독자가 등록 즉시 마지막 위치를 받음
class MapFragment : Fragment() {
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
// postSticky가 호출된 경우 즉시 LocationEvent를 받음
}
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은 모든 핸들러에 단일 백그라운드 스레드를 사용합니다 — 순차적으로 실행됩니다. ASYNC는 각 핸들러에 대해 풀에서 새 스레드를 생성합니다 — 병렬로 실행됩니다. BACKGROUND는 공유 데이터베이스가 있는 I/O 작업에 적합합니다. ASYNC는 독립적인 장기 실행 작업(네트워크 요청)에 적합합니다. 두 모드 모두 공유 리소스에 대한 스레드 안전 액세스가 필요합니다. 스레드 수를 염두에 두세요: ASYNC 풀은 무제한입니다.
EventBus를 사용할 때 개발자들은 메모리 누수, 예기치 않은 호출 및 성능 저하로 이어지는 실수를 자주 범합니다. 가장 중요한 것: Activity에서 등록 취소를 잊는 것, onCreate에서 등록(onStart/onStop 대신), Object(모든 이벤트) 구독, 무한 루프에서 이벤트 전송. Android Profiler를 사용한 프로파일링이 문제 식별에 도움이 됩니다.
가장 흔한 실수는 onDestroy()에서 등록 취소 없이 onCreate()에서 Activity를 등록하는 것입니다. 결과: EventBus가 Activity에 대한 참조를 보유하고 GC가 해제할 수 없습니다. 화면을 회전하면 이전 Activity가 메모리에 남아 있는 상태에서 새 Activity가 생성됩니다. 해결책: 항상 onStart/onStop에서 register/unregister를 쌍으로 사용하세요. Fragment의 경우 동일한 패턴을 사용합니다. finish 후 Activity가 EventBus에 의해 유지되는 경우 Memory Profiler로 확인하세요.
Subscriber Index가 없으면 EventBus는 매 register()마다 리플렉션을 사용하여 @Subscribe 메서드를 찾습니다. Android 6-7 기기에서는 리플렉션이 느려 최대 50ms의 지연이 발생합니다. Subscriber Index는 리플렉션을 완전히 제거합니다: 메서드는 애노테이션 프로세서를 통해 컴파일 타임에 인덱싱됩니다. 구독자가 20명 이상인 프로젝트의 경우 인덱스가 필수입니다. build.gradle에 kapt 또는 annotationProcessor가 구성되어 있는지 확인하세요.
// 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를 사용하는 최신 프로젝트는 kotlinx.coroutines 라이브러리의 SharedFlow와 Channel을 선호합니다. SharedFlow는 리플레이(sticky), 버퍼링 및 백프레셔를 지원합니다. Channel은 일회성 이벤트(toast, 탐색)를 처리합니다. 두 솔루션 모두 repeatOnLifecycle을 통해 Lifecycle과 통합되며 수동 구독 취소가 필요하지 않습니다. 새 프로젝트의 경우 EventBus 대신 SharedFlow가 권장됩니다. 기존 프로젝트의 경우 리팩토링 중 마이그레이션이 정당화됩니다.
자주 묻는 질문
EventBus는 모든 컴포넌트(Activity, Fragment, Service) 간 데이터 교환을 위한 이벤트 버스입니다. LiveData는 UI 컴포넌트가 관찰하는 데이터를 위한 lifecycle-aware 래퍼입니다. LiveData는 Lifecycle을 통해 구독을 자동으로 관리합니다. EventBus는 수동 register/unregister가 필요합니다. LiveData는 UI 레이어에, EventBus는 LiveData가 불편한 모듈 간 통신에 권장됩니다.
Sticky event는 전송 후에도 EventBus에 남아 있는 이벤트입니다. postSticky() 후에 등록된 새 구독자는 즉시 마지막 sticky event를 받습니다. 초기 상태에 사용됩니다: 화면을 열면 새 요청 없이 최신 데이터를 받습니다. removeStickyEvent()를 통해 또는 동일한 유형의 새 sticky event가 전송될 때 제거됩니다.
예, EventBus는 스레드 안전합니다. post()는 모든 스레드에서 호출할 수 있습니다. 구독자에 대한 이벤트 전달은 내부적으로 동기화됩니다. ThreadMode가 핸들러 실행 스레드를 결정합니다: 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는 KMM 프로젝트의 Android 측에서 작동하지만 commonMain에서는 사용할 수 없습니다. 크로스 플랫폼 이벤트의 경우 플랫폼 네이티브 메커니즘 또는 expect/actual을 통한 추상화를 선호하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.