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のアーキテクチャは、3つの主要要素で構成されています: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は、アノテーションプロセッサを介してコンパイル時にハンドライン dex を生成します。これによりリフレクションのオーバーヘッドが排除され、登録が高速化されます。有効にするには、build.gradleにeventbus-annotation-processorを追加します。EventBusは、classpathで利用可能な場合、自動的にインデックスを使用します。インデックスがなくてもライブラリは動作しますが、パフォーマンスがわずかに低下します。
EventBus.getDefault().post(event)が呼び出されると、ライブラリはイベントタイプを特定し、そのタイプを受け入れる@Subscribeメソッドを持つ登録済みの購読者をすべて見つけ、指定されたThreadModeに従ってそれらを呼び出します。購読者の検索は、登録時に構築されたClass → CopyOnWriteArrayList
購読者はonStart()で登録し、onStop()で登録を解除する必要があります。onCreate()で登録してonDestroy()で解除すると、finish()のためにonDestroyが呼び出されずに破棄されたActivityが購読者リストに残る可能性があります。購読者のリークはEventBusの主要な問題の1つです:購読者リストに残った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にイベントを送信する場合にも簡単です — 特にバックグラウンドタスクの進行状況を通知する必要がある場合に便利です。ライブラリのサイズは最小限(約50 KB)で、依存関係は追加されません。
LiveDataとFlowはAndroid Jetpackの一部であり、Lifecycleと統合されています。コンポーネントが破棄されると自動的に購読を解除し、メモリリークを排除します。Flowはコルーチンと複雑な変換演算子をサポートしています。GoogleはUIレイヤーにLiveData、リポジトリにFlowを推奨しています。EventBusは、ナビゲーションとビジネスロジックがMVVMに適合しないクロスモジュールイベントに引き続き有用です。
Subscribeは、@Subscribeアノテーションを介したイベントハンドラの登録です。メソッドはpublic、voidで、正確に1つのパラメータ(イベントタイプ)を受け入れる必要があります。Postは、EventBus.getDefault().post(event)を介して購読中のすべてのハンドラにイベントを送信することです。postメソッドは結果を返さず、呼び出されたハンドラの数も示しません。応答が必要なイベントの場合は、結果フィールドを持つ別のEventクラスを使用してください。
イベントは任意のJava/Kotlinクラスです。不変のイベントにはdata classを、可変フィールドを持つイベントには通常のクラスを使用することをお勧めします。イベントの命名はアクションを反映する必要があります:UserLoggedInEvent、DataLoadedEvent、NetworkErrorEvent。String typeフィールドを持つ単一の汎用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はすべてのハンドラに1つのバックグラウンドスレッドを使用します — これらは順次実行されます。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のデバイスではリフレクションが遅く、最大50 msの遅延が発生します。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は1回限りのイベント(トースト、ナビゲーション)を処理します。どちらのソリューションも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アプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。