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). الأحداث مُنمطة: تشترك المعالجات في فئة حدث محددة ويتم استدعاؤها فقط عند نشر حدث من تلك الفئة أو فئاتها الفرعية.

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 بدون خطأ — هذا سلوك آمن.

دورة حياة التسجيل

يجب أن يسجل المشترك في onStart() ويلغي التسجيل في onStop(). إذا سجلت في onCreate() وألغيت التسجيل في onDestroy()، فقد تبقى Activity تم تدميرها دون استدعاء onDestroy (بسبب finish()) في قائمة المشتركين. تسرب المشترك هو أحد المشكلات الرئيسية في EventBus: Activity المتبقية في قائمة المشتركين لن يتم جمعها بواسطة GC حتى تلغي التسجيل. قم دائمًا بإقران register/unregister في طرق دورة الحياة الصحيحة.

أولوية المعالجات

تدعم التعليقة @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 مقابل LocalBroadcastManager مقابل LiveData

يقدم Android عدة آليات للتواصل داخل العملية: EventBus وLocalBroadcastManager (مهمل) وLiveData/Flow. لكل منها مزاياها وعيوبها. يعتمد الاختيار على النهج المعماري ومتطلبات الأداء. تميل توصيات Google الحديثة نحو LiveData وFlow بسبب التكامل مع Lifecycle وغياب التسريبات.

الميزةEventBusLocalBroadcastManagerLiveData / Flow
الأنماطعبر فئة الحدثعبر مرشح Intent (String)عبر النوع العام
مراعي لدورة الحياةلا (إلغاء يدوي)لا (إلغاء يدوي)نعم (تلقائي)
Stickyنعم (postSticky)لانعم (LiveData دائمًا sticky)
ThreadModeMAIN, POSTING, BACKGROUND, ASYNCMain فقطعبر observe/observeOn
الأداءعالي (Subscriber Index)متوسط (غلاف IPC)عالي (المراقبة)

متى يكون EventBus أفضل

EventBus مفيد في المشاريع ذات الكود القديم الكبير وحيث LiveData/Flow غير متوفرة (مشاريع Java فقط). Sticky events في EventBus توفر مرونة غير موجودة في LocalBroadcastManager. EventBus أسهل أيضًا لإرسال الأحداث من Service إلى Activity بدون ViewModel — خاصة عندما تحتاج إلى إخطار بتقدم مهمة خلفية. المكتبة ذات حجم صغير (حوالي 50 كيلوبايت) ولا تضيف تبعيات.

متى يكون LiveData/Flow أفضل

LiveData وFlow هما جزء من Android Jetpack ومتكاملان مع Lifecycle. يلغيان الاشتراك تلقائيًا عند تدمير المكون، مما يزيل تسرب الذاكرة. يدعم Flow coroutines وعوامل تحويل معقدة. توصي Google بـ LiveData لطبقة UI وFlow للمستودعات. يبقى EventBus مفيدًا للأحداث عبر الوحدات حيث لا تتناسب الملاحة ومنطق الأعمال مع MVVM.

Subscribe وPost: العمليات الأساسية

Subscribe هو تسجيل معالج حدث عبر التعليقة @Subscribe. يجب أن تكون الطريقة عامة، من نوع void، وتقبل معاملًا واحدًا بالضبط — نوع الحدث. Post هو إرسال حدث إلى جميع المعالجات المشتركة عبر EventBus.getDefault().post(event). لا تُرجع طريقة post نتيجة ولا تشير إلى عدد المعالجات التي تم استدعاؤها. للأحداث مع رد، استخدم فئة Event منفصلة مع حقل للنتيجة.

إنشاء أحداث مخصصة

الحدث هو أي فئة Java/Kotlin. يُوصى باستخدام data class للأحداث غير القابلة للتغيير وفئة عادية للأحداث ذات الحقول القابلة للتغيير. يجب أن يعكس اسم الحدث الإجراء: 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 event من النوع المقابل. هذا مناسب لتمرير الحالة الأولية: عند فتح شاشة، تتلقى أحدث البيانات المرسلة قبل تسجيلها. يمكنك إزالة sticky event عبر EventBus.getDefault().removeStickyEvent(Class).

ThreadMode: أربعة أوضاع تنفيذ

ThreadMode يحدد أي خيط ينفذ المعالج. POSTING (افتراضي) — يُنفذ المعالج في نفس الخيط حيث تم استدعاء post. MAIN — يُنفذ المعالج في الخيط الرئيسي عبر Handler. BACKGROUND — يُنفذ المعالج في خيط خلفية؛ إذا تم استدعاء post في الخيط الرئيسي، يضع EventBus المعالج في قائمة انتظار خيط الخلفية. ASYNC — يُنفذ كل معالج في خيط خلفية منفصل من مجموعة خيوط. لتحديثات UI، استخدم MAIN.

kotlin
// Sticky Event
data class LocationEvent(val lat: Double, val lng: Double)

// إرسال sticky event من 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 event
EventBus.getDefault().removeStickyEvent(LocationEvent::class.java)

ThreadMode.BACKGROUND مقابل ASYNC

BACKGROUND يستخدم خيط خلفية واحدًا لجميع المعالجات — يتم تنفيذها بالتسلسل. ASYNC ينشئ خيطًا جديدًا من المجموعة لكل معالج — يتم تنفيذها بالتوازي. BACKGROUND مناسب لعمليات الإدخال/الإخراج مع قاعدة بيانات مشتركة. ASYNC للعمليات الطويلة المستقلة (طلبات الشبكة). كلا الوضعين يتطلبان وصولًا آمنًا للخيوط إلى الموارد المشتركة. ضع في اعتبارك عدد الخيوط: مجموعة ASYNC غير محدودة.

الأخطاء الشائعة وأداء EventBus

عند استخدام EventBus، غالبًا ما يرتكب المطورون أخطاء تؤدي إلى تسرب الذاكرة واستدعاءات غير متوقعة وتدهور الأداء. الأكثر خطورة: نسيان إلغاء التسجيل في Activity، والتسجيل في onCreate (بدلاً من onStart/onStop)، والاشتراك في Object (جميع الأحداث)، وإرسال الأحداث في حلقة لا نهائية. يساعد التنميط باستخدام Android Profiler في تحديد المشكلات.

تسرب الذاكرة عبر EventBus

الخطأ الأكثر شيوعًا هو تسجيل Activity في onCreate() دون إلغاء التسجيل في onDestroy(). النتيجة: 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 الانعكاس تمامًا: يتم فهرسة الطرق في وقت الترجمة عبر معالج التعليقات التوضيحية. للمشاريع التي تحتوي على 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 إعادة التشغيل (sticky) والتخزين المؤقت والضغط العكسي. Channel يعالج الأحداث لمرة واحدة (toast، التنقل). كلا الحلين متكاملان مع Lifecycle عبر repeatOnLifecycle ولا يتطلبان إلغاء اشتراك يدوي. للمشاريع الجديدة، يُوصى باستخدام SharedFlow بدلاً من EventBus. للمشاريع الحالية، الترحيل مبرر أثناء إعادة الهيكلة.

الأسئلة الشائعة

ما الفرق بين EventBus وLiveData؟

EventBus هو ناقل أحداث لتبادل البيانات بين أي مكونات (Activity، Fragment، Service). LiveData هو غلاف يراعي دورة الحياة للبيانات التي يراقبها مكون UI. يدير LiveData الاشتراك تلقائيًا عبر Lifecycle. يتطلب EventBus register/unregister يدويًا. يُوصى بـ LiveData لطبقة UI، وEventBus للتواصل عبر الوحدات حيث يكون LiveData غير ملائم.

ما هو sticky event؟

Sticky event هو حدث يبقى في EventBus بعد إرساله. المشتركون الجدد المسجلون بعد postSticky() يتلقون فورًا آخر sticky event. يُستخدم للحالة الأولية: عند فتح شاشة، تتلقى أحدث البيانات دون طلب جديد. يُزال عبر removeStickyEvent() أو عند إرسال sticky event جديد من نفس النوع.

هل EventBus آمن للخيوط؟

نعم، EventBus آمن للخيوط. يمكن استدعاء post() من أي خيط. يتم مزامنة تسليم الأحداث للمشتركين داخليًا. ThreadMode يحدد خيط تنفيذ المعالج: MAIN (الخيط الرئيسي عبر 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. للأحداث عبر المنصات، فضّل الآليات الأصلية للمنصة أو التجريد عبر 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 أفضل — فهي تراعي دورة الحياة وآمنة للخيوط.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا