EventBus là một thư viện dành cho Android triển khai mẫu Publisher-Subscriber thông qua bus sự kiện, cho phép trao đổi dữ liệu giữa các thành phần mà không có sự phụ thuộc trực tiếp. Được phát triển bởi GreenRobot, thư viện đơn giản hóa giao tiếp giữa Activity, Fragment, Service và Background Thread. Theo dữ liệu GitHub (2025), EventBus có hơn 25 nghìn sao và được sử dụng trong hàng nghìn ứng dụng Android. Các thao tác chính là subscribe (đăng ký nhận sự kiện), post (gửi sự kiện) và sticky event (sự kiện hoãn lại cho người đăng ký mới).
Những điểm chính
EventBus là một thư viện bus sự kiện cho Android triển khai mẫu Publisher-Subscriber. Nó cho phép truyền sự kiện giữa các thành phần ứng dụng (Activity, Fragment, Service, ViewModel) mà không tạo ra sự phụ thuộc rõ ràng giữa chúng. Không giống như các cơ chế Android tiêu chuẩn (Intent, BroadcastReceiver), EventBus hoạt động trong tiến trình và không sử dụng IPC. Thư viện được tối ưu hóa về hiệu suất và không sử dụng phản chiếu khi Subscriber Index được cấu hình đúng.
Kiến trúc EventBus bao gồm ba yếu tố chính: Event (lớp POJO với dữ liệu), Subscriber (đối tượng có các phương thức được chú thích @Subscribe) và EventBus (bộ điều phối trung tâm). Người đăng ký đăng ký qua EventBus.getDefault().register(this) và hủy đăng ký qua unregister(this). Các sự kiện được phân loại: trình xử lý đăng ký một lớp sự kiện cụ thể và chỉ được gọi khi một sự kiện của lớp đó hoặc các lớp con của nó được đăng.
// Sự kiện POJO
data class MessageEvent(
val message: String,
val timestamp: Long = System.currentTimeMillis()
)
// Người đăng ký trong 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
}
}
// Gửi sự kiện từ thành phần khác
EventBus.getDefault().post(MessageEvent("Hello from Service"))
Theo mặc định, EventBus sử dụng phản chiếu để tìm các phương thức @Subscribe trong register(). Subscriber Index tạo một chỉ mục trình xử lý tại thời điểm biên dịch thông qua bộ xử lý chú thích. Điều này loại bỏ chi phí phản chiếu và tăng tốc độ đăng ký. Để kích hoạt, hãy thêm eventbus-annotation-processor vào build.gradle. EventBus tự động sử dụng chỉ mục nếu có sẵn trong classpath. Nếu không có chỉ mục, thư viện vẫn hoạt động nhưng hiệu suất giảm nhẹ.
Khi EventBus.getDefault().post(event) được gọi, thư viện xác định loại sự kiện, tìm tất cả người đăng ký đã đăng ký có phương thức @Subscribe chấp nhận loại đó và gọi chúng theo ThreadMode đã chỉ định. Việc tìm kiếm người đăng ký được thực hiện bằng bản đồ Class → CopyOnWriteArrayList
Người đăng ký nên đăng ký trong onStart() và hủy đăng ký trong onStop(). Nếu bạn đăng ký trong onCreate() và hủy đăng ký trong onDestroy(), một Activity bị hủy mà không gọi onDestroy (do finish()) có thể vẫn nằm trong danh sách người đăng ký. Rò rỉ người đăng ký là một trong những vấn đề chính của EventBus: một Activity còn trong danh sách người đăng ký sẽ không được GC giải phóng cho đến khi hủy đăng ký. Luôn ghép cặp register/unregister trong các phương thức vòng đời chính xác.
Chú thích @Subscribe hỗ trợ tham số priority (số nguyên, mặc định 0). Các trình xử lý có ưu tiên cao hơn được gọi trước. cancelEventDelivery() cho phép ngăn chặn việc gửi sự kiện đến các người đăng ký còn lại. Điều này hữu ích cho các trình xử lý ưu tiên (ghi nhật ký, xác thực) có thể hủy xử lý sự kiện bởi các người đăng ký phía sau. Chức năng này chỉ khả dụng trong luồng đăng sự kiện.
// Ví dụ phức tạp với ưu tiên
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)
}
}
// Gửi sự kiện
EventBus.getDefault().post(NavigationEvent("profile", bundle))
Android cung cấp một số cơ chế cho giao tiếp trong tiến trình: EventBus, LocalBroadcastManager (không được khuyến khích) và LiveData/Flow. Mỗi cơ chế có ưu điểm và nhược điểm riêng. Sự lựa chọn phụ thuộc vào cách tiếp cận kiến trúc và yêu cầu về hiệu suất. Các khuyến nghị hiện đại của Google nghiêng về LiveData và Flow do tích hợp với Lifecycle và không có rò rỉ.
| Đặc điểm | EventBus | LocalBroadcastManager | LiveData / Flow |
|---|---|---|---|
| Phân loại | Qua lớp sự kiện | Qua bộ lọc Intent (String) | Qua kiểu generic |
| Lifecycle-aware | Không (hủy thủ công) | Không (hủy thủ công) | Có (tự động) |
| Sticky | Có (postSticky) | Không | Có (LiveData luôn sticky) |
| ThreadMode | MAIN, POSTING, BACKGROUND, ASYNC | Chỉ main | Qua observe/observeOn |
| Hiệu suất | Cao (Subscriber Index) | Trung bình (lớp bọc IPC) | Cao (quan sát) |
EventBus hữu ích trong các dự án có nhiều mã kế thừa và khi LiveData/Flow không khả dụng (các dự án chỉ Java). Sticky events của EventBus cung cấp tính linh hoạt mà LocalBroadcastManager thiếu. EventBus cũng dễ dàng hơn để gửi sự kiện từ Service đến Activity mà không cần ViewModel — đặc biệt khi bạn cần thông báo về tiến trình của tác vụ nền. Thư viện có kích thước tối thiểu (khoảng 50 KB) và không thêm phụ thuộc.
LiveData và Flow là một phần của Android Jetpack và được tích hợp với Lifecycle. Chúng tự động hủy đăng ký khi một thành phần bị hủy, loại bỏ rò rỉ bộ nhớ. Flow hỗ trợ coroutine và các toán tử chuyển đổi phức tạp. Google khuyến nghị LiveData cho lớp UI và Flow cho kho lưu trữ. EventBus vẫn hữu ích cho các sự kiện xuyên mô-đun nơi điều hướng và logic kinh doanh không phù hợp với MVVM.
Subscribe là đăng ký một trình xử lý sự kiện thông qua chú thích @Subscribe. Phương thức phải là public, void và chấp nhận chính xác một tham số — loại sự kiện. Post là gửi một sự kiện đến tất cả các trình xử lý đã đăng ký qua EventBus.getDefault().post(event). Phương thức post không trả về kết quả và không cho biết có bao nhiêu trình xử lý đã được gọi. Đối với sự kiện có phản hồi, hãy sử dụng một lớp Event riêng với trường kết quả.
Một sự kiện là bất kỳ lớp Java/Kotlin nào. Nên sử dụng data class cho các sự kiện bất biến và lớp thông thường cho các sự kiện có trường có thể thay đổi. Tên sự kiện nên phản ánh hành động: UserLoggedInEvent, DataLoadedEvent, NetworkErrorEvent. Tránh một lớp Event chung duy nhất với trường String type — điều này loại bỏ lợi ích của việc phân loại. Hệ thống phân cấp sự kiện (Event cha) cho phép đăng ký một nhóm các sự kiện liên quan.
// Hệ thống phân cấp sự kiện
open class UserEvent
data class UserLoggedIn(val userId: String) : UserEvent()
data class UserLoggedOut(val reason: String) : UserEvent()
// Đăng ký lớp cơ sở
class SessionManager {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onUserEvent(event: UserEvent) {
when (event) {
is UserLoggedIn -> startSession(event.userId)
is UserLoggedOut -> endSession(event.reason)
}
}
}
// Gửi
EventBus.getDefault().post(UserLoggedIn("user_123"))
Gọi EventBus.getDefault().register(this) sẽ quét lớp người đăng ký qua phản chiếu hoặc Subscriber Index và lưu trữ các phương thức @Subscribe tìm thấy vào bản đồ sự kiện. Unregister xóa người đăng ký khỏi bản đồ. Đăng ký lại mà không hủy đăng ký là một lỗi (sẽ ném MultipleSubscriberException). Đối với Fragment, hãy đăng ký trong onStart() và hủy trong onStop(). Đối với Service, trong onCreate() và onDestroy(). Đối với ViewModel, không được khuyến khích — hãy sử dụng LiveData.
Sticky event là một sự kiện tồn tại trong EventBus sau khi được gửi. Những người đăng ký mới đăng ký sau postSticky() ngay lập tức nhận được sticky event cuối cùng của loại tương ứng. Điều này thuận tiện để truyền trạng thái ban đầu: khi mở một màn hình, nó nhận được dữ liệu mới nhất được gửi trước khi đăng ký. Bạn có thể xóa một sticky event qua EventBus.getDefault().removeStickyEvent(Class).
ThreadMode xác định luồng nào thực thi trình xử lý. POSTING (mặc định) — trình xử lý chạy trong cùng luồng nơi post được gọi. MAIN — trình xử lý chạy trong luồng chính qua Handler. BACKGROUND — trình xử lý chạy trong luồng nền; nếu post được gọi trong luồng chính, EventBus đặt trình xử lý vào hàng đợi luồng nền. ASYNC — mỗi trình xử lý chạy trong một luồng nền riêng biệt từ một nhóm luồng. Để cập nhật UI, hãy sử dụng MAIN.
// Sticky Event
data class LocationEvent(val lat: Double, val lng: Double)
// Gửi sticky event từ LocationService
EventBus.getDefault().postSticky(LocationEvent(55.7558, 37.6173))
// Người đăng ký nhận vị trí cuối cùng ngay sau khi đăng ký
class MapFragment : Fragment() {
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
// Sẽ nhận ngay LocationEvent nếu postSticky đã được gọi
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onLocationEvent(event: LocationEvent) {
moveMapTo(event.lat, event.lng)
}
}
// Xóa sticky event
EventBus.getDefault().removeStickyEvent(LocationEvent::class.java)
BACKGROUND sử dụng một luồng nền duy nhất cho tất cả các trình xử lý — chúng thực thi tuần tự. ASYNC tạo một luồng mới từ nhóm cho mỗi trình xử lý — chúng thực thi song song. BACKGROUND phù hợp cho các thao tác I/O với cơ sở dữ liệu dùng chung. ASYNC dành cho các thao tác dài độc lập (yêu cầu mạng). Cả hai chế độ đều yêu cầu truy cập an toàn luồng đến tài nguyên dùng chung. Hãy lưu ý số lượng luồng: nhóm ASYNC là không giới hạn.
Khi sử dụng EventBus, các nhà phát triển thường mắc lỗi dẫn đến rò rỉ bộ nhớ, gọi không mong muốn và giảm hiệu suất. Quan trọng nhất: quên hủy đăng ký trong Activity, đăng ký trong onCreate (thay vì onStart/onStop), đăng ký Object (tất cả sự kiện), gửi sự kiện trong vòng lặp vô hạn. Hồ sơ với Android Profiler giúp xác định vấn đề.
Lỗi phổ biến nhất là đăng ký Activity trong onCreate() mà không hủy đăng ký trong onDestroy(). Kết quả: EventBus giữ tham chiếu đến Activity, GC không thể giải phóng nó. Khi xoay màn hình, một Activity mới được tạo trong khi Activity trước đó vẫn còn trong bộ nhớ. Giải pháp: luôn ghép cặp register/unregister trong onStart/onStop. Đối với Fragment, sử dụng cùng mẫu. Nếu một Activity bị EventBus giữ lại sau finish, hãy kiểm tra bằng Memory Profiler.
Nếu không có Subscriber Index, EventBus sử dụng phản chiếu để tìm các phương thức @Subscribe mỗi lần register(). Trên các thiết bị Android 6-7, phản chiếu chậm, gây ra độ trễ lên đến 50 ms. Subscriber Index loại bỏ hoàn toàn phản chiếu: các phương thức được lập chỉ mục tại thời điểm biên dịch thông qua bộ xử lý chú thích. Đối với các dự án có 20+ người đăng ký, chỉ mục là bắt buộc. Đảm bảo rằng kapt hoặc annotationProcessor được cấu hình trong build.gradle.
// build.gradle (app) — thêm Subscriber Index
dependencies {
implementation 'org.greenrobot:eventbus:3.3.1'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.3.1'
}
// Đối với Kotlin sử dụng kapt
plugins {
id 'kotlin-kapt'
}
dependencies {
implementation 'org.greenrobot:eventbus:3.3.1'
kapt 'org.greenrobot:eventbus-annotation-processor:3.3.1'
}
// Cấu hình chỉ mục (trong defaultConfig)
kapt {
arguments {
arg('eventBusIndex', 'com.app.EventBusIndex')
}
}
Các dự án hiện đại sử dụng Kotlin và Jetpack Compose ưa chuộng SharedFlow và Channel từ thư viện kotlinx.coroutines. SharedFlow hỗ trợ phát lại (sticky), đệm và backpressure. Channel xử lý các sự kiện một lần (toast, điều hướng). Cả hai giải pháp đều được tích hợp với Lifecycle qua repeatOnLifecycle và không yêu cầu hủy đăng ký thủ công. Đối với các dự án mới, SharedFlow được khuyến nghị thay vì EventBus. Đối với các dự án hiện có, việc di chuyển là hợp lý trong quá trình tái cấu trúc.
Các câu hỏi thường gặp
EventBus là một bus sự kiện để trao đổi dữ liệu giữa bất kỳ thành phần nào (Activity, Fragment, Service). LiveData là một lớp bọc lifecycle-aware cho dữ liệu được quan sát bởi một thành phần UI. LiveData tự động quản lý đăng ký thông qua Lifecycle. EventBus yêu cầu register/unregister thủ công. LiveData được khuyến nghị cho lớp UI, EventBus cho giao tiếp xuyên mô-đun nơi LiveData bất tiện.
Sticky event là một sự kiện tồn tại trong EventBus sau khi được gửi. Những người đăng ký mới sau postSticky() ngay lập tức nhận được sticky event cuối cùng. Nó được sử dụng cho trạng thái ban đầu: khi mở một màn hình, nó nhận được dữ liệu mới nhất mà không cần yêu cầu mới. Nó được xóa qua removeStickyEvent() hoặc khi một sticky event mới cùng loại được gửi.
Có, EventBus an toàn cho luồng. Có thể gọi post() từ bất kỳ luồng nào. Việc gửi sự kiện đến người đăng ký được đồng bộ hóa nội bộ. ThreadMode xác định luồng thực thi của trình xử lý: MAIN (luồng chính qua Handler), POSTING (luồng gọi), BACKGROUND (hàng đợi tác vụ nền), ASYNC (luồng riêng). Để cập nhật UI, hãy sử dụng MAIN. Đối với các thao tác nặng, hãy sử dụng ASYNC.
Bật ghi nhật ký qua EventBus.builder().logNoSubscriberMessages(true).sendNoSubscriberEvent(true).install(). Đăng ký NoSubscriberEvent để theo dõi các sự kiện không có trình xử lý. Sử dụng SubscriberExceptionEvent để xử lý ngoại lệ toàn cục. Android Profiler giúp tìm rò rỉ. Đối với các kịch bản phức tạp, hãy viết bài kiểm tra: EventBus.getDefault().register(mock) + post(event) + verify(mock).
Không, EventBus (GreenRobot) bị ràng buộc với Android SDK và JVM. Đối với Kotlin Multiplatform, hãy sử dụng Kotlin Multiplatform SharedFlow hoặc KMMBus — các thư viện hỗ trợ mã dùng chung. EventBus hoạt động ở phía Android của dự án KMM nhưng không khả dụng trong commonMain. Đối với các sự kiện đa nền tảng, hãy ưu tiên cơ chế gốc của nền tảng hoặc trừu tượng hóa qua expect/actual.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm