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 به درستی پیکربندی شده از reflection استفاده نمیکند.
معماری EventBus از سه عنصر کلیدی تشکیل شده است: Event (کلاس POJO با داده)، Subscriber (شیء با متدهای علامتگذاری شده با @Subscribe) و EventBus (توزیعکننده مرکزی). مشترک از طریق EventBus.getDefault().register(this) ثبت نام میکند و از طریق unregister(this) لغو ثبت میکند. رویدادها تایپشده هستند: handlerها روی کلاس خاصی از رویداد مشترک میشوند و فقط هنگام 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 از reflection برای یافتن متدهای @Subscribe در register() استفاده میکند. Subscriber Index یک index از handlerها در مرحله کامپایل از طریق annotation processor تولید میکند. این کار سربار reflection را حذف میکند و ثبت نام را سریعتر میکند. برای فعالسازی، eventbus-annotation-processor را به build.gradle اضافه کنید. EventBus به طور خودکار از index استفاده میکند اگر در classpath موجود باشد. بدون index کتابخانه همچنان کار میکند اما با کاهش عملکرد جزئی.
هنگام فراخوانی EventBus.getDefault().post(event)، کتابخانه نوع رویداد را تعیین میکند، تمام مشترکین ثبتشده با متدهای @Subscribe که این نوع را میپذیرند پیدا میکند و آنها را مطابق ThreadMode مشخص شده فراخوانی میکند. جستجوی مشترکین بر اساس نقشه Class → CopyOnWriteArrayList
مشترک باید در onStart() ثبت نام کند و در onStop() لغو ثبت نام کند. اگر در onCreate() ثبت نام کند و در onDestroy() لغو ثبت نام کند، Activity که بدون فراخوانی onDestroy (به دلیل finish()) از بین رفته ممکن است در لیست مشترکین باقی بماند. نشت مشترک — یکی از مشکلات اصلی EventBus: Activity باقیمانده در لیست مشترکین تا زمانی که لغو ثبت نام نکند توسط GC جمعآوری نخواهد شد. همیشه register/unregister را در متدهای lifecycle صحیح جفت کنید.
annotation @Subscribe از پارامتر priority (عدد صحیح، پیشفرض 0) پشتیبانی میکند. handlerهای با اولویت بالاتر زودتر فراخوانی میشوند. cancelEventDelivery() امکان قطع تحویل رویداد به سایر مشترکین را فراهم میکند. این برای handlerهای اولویتدار (لاگینگ، احراز هویت) مفید است که میتوانند پردازش رویداد توسط مشترکین پایینتر را لغو کنند. این تابع فقط در رشته ارسال رویداد در دسترس است.
// مثال پیچیده با اولویت
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 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 KB) دارد و وابستگی اضافه نمیکند.
LiveData و Flow بخشی از Android Jetpack هستند و با Lifecycle یکپارچه شدهاند. آنها به طور خودکار هنگام نابودی کامپوننت لغو ثبت میشوند و نشت حافظه را حذف میکنند. Flow از کوروتینها و عملگرهای تبدیل پیچیده پشتیبانی میکند. Google LiveData را برای لایه UI و Flow را برای مخازن توصیه میکند. EventBus برای رویدادهای بین ماژولی باقی میماند جایی که ناوبری و منطق کسب و کار در MVVM نمیگنجد.
Subscribe — ثبت handler رویداد از طریق annotation @Subscribe. متد باید public، void باشد و دقیقاً یک پارامتر — نوع رویداد — را بپذیرد. Post — ارسال رویداد به تمام handlerهای مشترک از طریق EventBus.getDefault().post(event). متد post نتیجهای برنمیگرداند و اطلاع نمیدهد که چند handler فراخوانی شدهاند. برای رویدادهای با پاسخ، از کلاس Event جداگانه با فیلد نتیجه استفاده کنید.
رویداد — هر کلاس Java/Kotlin است. توصیه میشود از data 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) کلاس مشترک را از طریق reflection یا Subscriber Index اسکن میکند و متدهای @Subscribe یافتشده را در نقشه رویدادها ذخیره میکند. Unregister مشترک را از نقشه حذف میکند. ثبت نام مجدد بدون لغو ثبت نام — خطا است (MultipleSubscriberException رخ میدهد). برای Fragment در onStart() ثبت نام کنید و در onStop() لغو ثبت نام کنید. برای Service — در onCreate() و onDestroy(). برای ViewModel توصیه نمیشود — از LiveData استفاده کنید.
Sticky event — رویدادی است که پس از ارسال در EventBus ذخیره میشود. مشترکین جدید که بعد از postSticky() ثبت نام میکنند بلافاصله آخرین sticky-رویداد از نوع مربوطه را دریافت میکنند. این برای انتقال حالت اولیه مفید است: هنگام باز کردن صفحه، آخرین دادههای ارسالشده قبل از ثبت نام آن را دریافت میکند. حذف sticky-رویداد از طریق EventBus.getDefault().removeStickyEvent(Class) امکانپذیر است.
ThreadMode تعیین میکند که handler در کدام رشته فراخوانی شود. POSTING (پیشفرض) — handler در همان رشتهای که post فراخوانی شده اجرا میشود. MAIN — handler در main thread از طریق Handler اجرا میشود. BACKGROUND — handler در background thread اجرا میشود؛ اگر post در main thread فراخوانی شده باشد، EventBus handler را در صف background thread قرار میدهد. ASYNC — هر handler در یک 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)
// در صورت وجود 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 از یک background-thread برای همه handlerها استفاده میکند — آنها به صورت ترتیبی اجرا میشوند. ASYNC برای هر handler یک رشته جدید از استخر ایجاد میکند — آنها به صورت موازی اجرا میشوند. BACKGROUND برای عملیات ورودی-خروجی با پایگاه داده مشترک مناسب است. ASYNC — برای عملیات طولانی مستقل (درخواستهای شبکه). هر دو حالت نیاز به دسترسی thread-safe به منابع مشترک دارند. تعداد رشتهها: استخر ASYNC نامحدود است.
هنگام استفاده از EventBus، توسعهدهندگان اغلب مرتکب خطاهایی میشوند که منجر به نشت حافظه، فراخوانیهای غیرمنتظره و کاهش عملکرد میشود. بحرانیترین آنها: فراموش کردن لغو ثبت نام در Activity، ثبت نام در onCreate (به جای onStart/onStop)، اشتراک در Object (همه رویدادها)، ارسال رویدادها در حلقه بینهایت. پروفایلینگ از طریق Android Profiler به شناسایی مشکلات کمک میکند.
رایجترین خطا — ثبت نام Activity در onCreate() بدون لغو ثبت نام در onDestroy(). نتیجه: EventBus reference Activity را نگه میدارد، GC نمیتواند آن را آزاد کند. هنگام چرخش صفحه، Activity جدید ایجاد میشود، قبلی در حافظه باقی میماند. راه حل: همیشه register/unregister را در onStart/onStop جفت کنید. برای Fragment از همان طرح استفاده کنید. اگر Activity پس از finish توسط EventBus نگه داشته شده است، از طریق Memory Profiler بررسی کنید.
بدون Subscriber Index، EventBus از reflection برای یافتن متدهای @Subscribe در هر register() استفاده میکند. در دستگاههای Android 6-7، reflection به کندی کار میکند و باعث تأخیر تا 50 ms میشود. Subscriber Index reflection را کاملاً حذف میکند: متدها در مرحله کامپایل از طریق annotation processor ایندکس میشوند. برای پروژههای با 20+ مشترک، index اجباری است. بررسی کنید که 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'
}
// پیکربندی index (در 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 thread-safe است. فراخوانی post() از هر رشتهای امکانپذیر است. تحویل رویداد به مشترکین در داخل کتابخانه همگامسازی میشود. ThreadMode رشته اجرای handler را تعیین میکند: MAIN (main thread از طریق Handler)، POSTING (رشته فرستنده)، BACKGROUND (صف وظایف پسزمینه)، ASYNC (رشته جداگانه). برای بهروزرسانیهای UI از MAIN، برای عملیات سنگین از ASYNC استفاده کنید.
لاگینگ را از طریق EventBus.builder().logNoSubscriberMessages(true).sendNoSubscriberEvent(true).install() فعال کنید. برای ردیابی رویدادهای بدون handler، در 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 از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید