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 είναι σωστά ρυθμισμένο.
Η αρχιτεκτονική του 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 χρησιμοποιεί αντανάκλαση για την εύρεση μεθόδων @Subscribe κατά την register(). Subscriber Index δημιουργεί ένα ευρετήριο χειριστών κατά τη φάση μεταγλώττισης μέσω ενός επεξεργαστή σχολιασμών. Αυτό εξαλείφει την επιβάρυνση της αντανάκλασης και επιταχύνει την εγγραφή. Για ενεργοποίηση, προσθέστε το eventbus-annotation-processor στο build.gradle. Το EventBus χρησιμοποιεί αυτόματα το ευρετήριο αν είναι διαθέσιμο στο classpath. Χωρίς ευρετήριο, η βιβλιοθήκη εξακολουθεί να λειτουργεί, αλλά με ελαφρά μείωση απόδοσης.
Κατά την κλήση EventBus.getDefault().post(event), η βιβλιοθήκη καθορίζει τον τύπο συμβάντος, βρίσκει όλους τους εγγεγραμμένους συνδρομητές με μεθόδους @Subscribe που αποδέχονται αυτόν τον τύπο και τους καλεί σύμφωνα με το καθορισμένο ThreadMode. Η αναζήτηση συνδρομητών πραγματοποιείται μέσω του χάρτη Class → CopyOnWriteArrayList
Ο συνδρομητής πρέπει να εγγραφεί στο onStart() και να διαγραφεί στο onStop(). Εάν εγγραφεί στο onCreate() και διαγραφεί στο onDestroy(), ένα Activity που καταστράφηκε χωρίς κλήση onDestroy (λόγω finish()) μπορεί να παραμείνει στη λίστα συνδρομητών. Διαρροή συνδρομητή — ένα από τα κύρια προβλήματα του 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 κλίνουν προς LiveData και Flow λόγω ενσωμάτωσης με το Lifecycle και απουσίας διαρροών.
| Χαρακτηριστικό | EventBus | LocalBroadcastManager | LiveData / Flow |
|---|---|---|---|
| Τυποποίηση | Μέσω κλάσης συμβάντος | Μέσω Intent filter (String) | Μέσω γενικού τύπου |
| 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 υποστηρίζει coroutines και σύνθετους τελεστές μετασχηματισμού. Η Google συνιστά LiveData για το επίπεδο UI και Flow για τα αποθετήρια. Το EventBus παραμένει για διαμονιδικά συμβάντα όπου η πλοήγηση και η επιχειρησιακή λογική δεν ταιριάζουν στο MVVM.
Subscribe — εγγραφή χειριστή συμβάντος μέσω του σχολίου @Subscribe. Η μέθοδος πρέπει να είναι public, void και να δέχεται ακριβώς μία παράμετρο — τον τύπο συμβάντος. Post — αποστολή συμβάντος σε όλους τους εγγεγραμμένους χειριστές μέσω EventBus.getDefault().post(event). Η μέθοδος post δεν επιστρέφει αποτέλεσμα και δεν ενημερώνει πόσοι χειριστές κλήθηκαν. Για συμβάντα με απάντηση, χρησιμοποιήστε ξεχωριστή κλάση Event με πεδίο για το αποτέλεσμα.
Συμβάν — οποιαδήποτε κλάση Java/Kotlin. Συνιστάται η χρήση data class για αμετάβλητα συμβάντα και κανονικής κλάσης για συμβάντα με μεταβλητά πεδία. Η ονομασία συμβάντων πρέπει να αντικατοπτρίζει την ενέργεια: 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) σαρώνει την κλάση του συνδρομητή μέσω αντανάκλασης ή Subscriber Index και αποθηκεύει τις μεθόδους @Subscribe που βρέθηκαν στο χάρτη συμβάντων. Unregister αφαιρεί τον συνδρομητή από το χάρτη. Εκ νέου εγγραφή χωρίς διαγραφή — σφάλμα (θα εμφανιστεί MultipleSubscriberException). Για Fragment, εγγραφείτε στο onStart() και διαγραφείτε στο onStop(). Για Service — στο onCreate() και onDestroy(). Για ViewModel δεν συνιστάται — χρησιμοποιήστε LiveData.
Sticky event — ένα συμβάν που διατηρείται στο EventBus μετά την αποστολή. Οι νέοι συνδρομητές που εγγράφονται μετά το postSticky() λαμβάνουν αμέσως το τελευταίο sticky-συμβάν του αντίστοιχου τύπου. Αυτό είναι βολικό για τη μεταφορά αρχικής κατάστασης: κατά το άνοιγμα μιας οθόνης, λαμβάνει τα τελευταία δεδομένα που στάλθηκαν πριν από την εγγραφή της. Η διαγραφή sticky-συμβάντος γίνεται μέσω EventBus.getDefault().removeStickyEvent(Class).
Το ThreadMode καθορίζει σε ποιο νήμα καλείται ο χειριστής. POSTING (προεπιλογή) — ο χειριστής εκτελείται στο ίδιο νήμα όπου κλήθηκε το post. MAIN — ο χειριστής εκτελείται στο main νήμα μέσω Handler. BACKGROUND — ο χειριστής εκτελείται σε background νήμα· αν το post κλήθηκε σε main νήμα, το EventBus τοποθετεί τον χειριστή στην ουρά background νήματος. ASYNC — κάθε χειριστής εκτελείται σε ξεχωριστό background νήμα από την ομάδα νημάτων. Για ενημερώσεις 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)
// Θα λάβει αμέσως 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)
BACKGROUND χρησιμοποιεί ένα background νήμα για όλους τους χειριστές — εκτελούνται διαδοχικά. ASYNC δημιουργεί ένα νέο νήμα από την ομάδα για κάθε χειριστή — εκτελούνται παράλληλα. Το BACKGROUND είναι κατάλληλο για λειτουργίες εισόδου-εξόδου με κοινή βάση δεδομένων. ASYNC — για ανεξάρτητες μεγάλες λειτουργίες (αιτήματα δικτύου). Και οι δύο τρόποι απαιτούν ασφαλή πρόσβαση νήματος σε κοινόχρηστους πόρους. Αριθμός νημάτων: η ομάδα ASYNC είναι απεριόριστη.
Κατά τη χρήση του EventBus, οι προγραμματιστές συχνά κάνουν λάθη που οδηγούν σε διαρροές μνήμης, απροσδόκητες κλήσεις και μείωση απόδοσης. Τα πιο κρίσιμα: ξεχασμένη διαγραφή σε Activity, εγγραφή σε onCreate (αντί onStart/onStop), εγγραφή σε Object (όλα τα συμβάντα), αποστολή συμβάντων σε ατελείωτο βρόχο. Η προφίλοποίηση μέσω Android Profiler βοηθά στον εντοπισμό προβλημάτων.
Το πιο συνηθισμένο λάθος — εγγραφή Activity στο onCreate() χωρίς διαγραφή στο onDestroy(). Αποτέλεσμα: το EventBus διατηρεί αναφορά στο Activity, ο GC δεν μπορεί να την ελευθερώσει. Κατά την περιστροφή οθόνης, δημιουργείται νέο Activity, το προηγούμενο παραμένει στη μνήμη. Λύση: πάντα να ζευγαρώνετε register/unregister σε onStart/onStop. Για Fragment, χρησιμοποιήστε το ίδιο σχήμα. Εάν το Activity κρατιέται από το EventBus μετά το finish, ελέγξτε μέσω Memory Profiler.
Χωρίς Subscriber Index, το EventBus χρησιμοποιεί αντανάκλαση για την εύρεση μεθόδων @Subscribe σε κάθε register(). Σε συσκευές με Android 6-7, η αντανάκλαση λειτουργεί αργά, προκαλώντας καθυστερήσεις έως 50 ms. Το Subscriber Index εξαλείφει εντελώς την αντανάκλαση: οι μέθοδοι ευρετηριάζονται κατά τη φάση μεταγλώττισης μέσω επεξεργαστή σχολιασμών. Για έργα με 20+ συνδρομητές, το ευρετήριο είναι υποχρεωτικό. Ελέγξτε ότι το 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'
}
// Ρύθμιση ευρετηρίου (στο 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 είναι ένα περιτύλιγμα με επίγνωση κύκλου ζωής για δεδομένα που παρατηρούνται από ένα στοιχείο UI. Το LiveData διαχειρίζεται αυτόματα την εγγραφή μέσω Lifecycle. Το EventBus απαιτεί χειροκίνητο register/unregister. Το LiveData συνιστάται για το επίπεδο UI, το EventBus — για διαμονιδική επικοινωνία όπου το LiveData δεν είναι βολικό.
Sticky event — ένα συμβάν που διατηρείται στο EventBus μετά την αποστολή. Οι νέοι συνδρομητές που εγγράφονται μετά το postSticky() λαμβάνουν αμέσως το τελευταίο sticky-συμβάν. Χρησιμοποιείται για αρχική κατάσταση: κατά το άνοιγμα οθόνης, λαμβάνει τα τελευταία δεδομένα χωρίς επαναλαμβανόμενο αίτημα. Διαγράφεται μέσω removeStickyEvent() ή κατά την αποστολή νέου sticky-συμβάντος ίδιου τύπου.
Ναι, το EventBus είναι thread-safe. Η κλήση post() είναι δυνατή από οποιοδήποτε νήμα. Η παράδοση συμβάντος στους συνδρομητές συγχρονίζεται εντός της βιβλιοθήκης. Το ThreadMode καθορίζει το νήμα εκτέλεσης του χειριστή: MAIN (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 στην πλευρά Android ενός έργου KMM λειτουργεί, αλλά δεν είναι διαθέσιμο στο commonMain. Για διαπλατφορμικά συμβάντα, προτιμώνται οι εγγενείς μηχανισμοί πλατφόρμας ή η αφαίρεση μέσω expect/actual.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης