EventBus: nó là gì, nguyên lý hoạt động và bus sự kiện Android

Tác giả: IT Sectr Đã đăng: 2026-03-18 Thời gian đọc: 10 phút

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à thư viện bus sự kiện cho giao tiếp liên kết lỏng lẻo trong Android.
  • @Subscribe là một chú thích đánh dấu phương thức là trình xử lý cho một loại sự kiện cụ thể.
  • EventBus.getDefault().post() gửi một sự kiện đến tất cả các trình xử lý đã đăng ký.
  • Sticky event giữ lại sự kiện cuối cùng để gửi đến người đăng ký mới.
  • ThreadMode xác định luồng thực thi của trình xử lý: MAIN, POSTING, BACKGROUND, ASYNC.

EventBus là gì?

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.

GreenRobot EventBus: kiến trúc

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.

kotlin
// 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"))

Subscriber Index cho hiệu suất

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ẹ.

EventBus hoạt động như thế nào trong Android?

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 được xây dựng trong quá trình đăng ký. Nếu một sự kiện không có người đăng ký, post hoàn thành mà không có lỗi — đây là hành vi an toàn.

Vòng đời đăng ký

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.

Ưu tiên trình xử lý

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.

kotlin
// 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))

EventBus vs LocalBroadcastManager vs LiveData

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ểmEventBusLocalBroadcastManagerLiveData / Flow
Phân loạiQua lớp sự kiệnQua bộ lọc Intent (String)Qua kiểu generic
Lifecycle-awareKhông (hủy thủ công)Không (hủy thủ công)Có (tự động)
StickyCó (postSticky)KhôngCó (LiveData luôn sticky)
ThreadModeMAIN, POSTING, BACKGROUND, ASYNCChỉ mainQua observe/observeOn
Hiệu suấtCao (Subscriber Index)Trung bình (lớp bọc IPC)Cao (quan sát)

Khi nào EventBus được ưu tiên

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.

Khi nào LiveData/Flow được ưu tiên

LiveDataFlow 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 và Post: các thao tác cơ bản

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ả.

Tạo sự kiện tùy chỉnh

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.

kotlin
// 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"))

Đăng ký và hủy đăng ký

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 Events và ThreadMode

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: bốn chế độ thực thi

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.

kotlin
// 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)

ThreadMode.BACKGROUND vs ASYNC

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.

Các lỗi thường gặp và hiệu suất của EventBus

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 đề.

Rò rỉ bộ nhớ qua EventBus

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.

Hiệu suất: Subscriber Index

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.

groovy
// 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 lựa chọn thay thế EventBus trong Android hiện đại

Các dự án hiện đại sử dụng Kotlin và Jetpack Compose ưa chuộng SharedFlowChannel 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

Sự khác biệt giữa EventBus và LiveData là gì?

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à gì?

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.

EventBus có an toàn cho luồng không?

, 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.

Làm thế nào để gỡ lỗi EventBus?

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).

Có thể sử dụng EventBus trong Kotlin Multiplatform không?

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

  • EventBus là thư viện Publisher-Subscriber cho Android triển khai bus sự kiện với phân loại qua các lớp POJO.
  • Chú thích @Subscribe với các tham số threadMode, sticky, priority xác định hành vi của trình xử lý sự kiện.
  • post() gửi một sự kiện đến tất cả người đăng ký đồng bộ; postSticky() giữ lại sự kiện cho người đăng ký mới.
  • ThreadMode kiểm soát luồng thực thi: POSTING (luồng gọi), MAIN (UI), BACKGROUND (hàng đợi), ASYNC (nhóm).
  • Subscriber Index qua bộ xử lý chú thích loại bỏ phản chiếu và tăng tốc đăng ký.
  • Rò rỉ bộ nhớ được ngăn chặn bằng cách ghép cặp register/unregister trong onStart/onStop của Activity hoặc Fragment.
  • Đối với các dự án mới, SharedFlow/Channel từ kotlinx.coroutines được ưu tiên hơn — chúng lifecycle-aware và an toàn cho luồng.

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.

Thảo luận dự án

Đọc thêm