A Lightweight Event Bus for Android: Simplify Component Communication
This article introduces a lightweight event notification framework for Android, explaining its design, core interfaces, event manager implementation, usage examples with login/logout scenarios, lifecycle considerations, duplicate event checks, and compares it to traditional observer patterns.
Preface
During development, communication between components often becomes complex, especially across modules or threads, leading to high coupling when using simple parameter passing or callbacks. To decouple event publishing and subscribing, a lightweight event notification mechanism is introduced.
Implementation
Event notification consists of defining events, registering/unregistering observers, and dispatching events. A simple interface and an event manager class are sufficient.
Define Interface
interface Observer {
fun onEvent(event: Int, vararg args: Any?)
fun listEvents(): IntArray
}The interface provides listEvents to return interested events and onEvent to receive the event and optional parameters.
Event Management
object EventManager {
private val HANDLER = Handler(Looper.getMainLooper())
private val OBSERVER_ARRAY = SparseArray<LinkedList<Observer>>(16)
@Synchronized
fun register(observer: Observer?) {
observer?.listEvents()?.forEach { event ->
var observerList = OBSERVER_ARRAY.get(event)
if (observerList == null) {
observerList = LinkedList()
OBSERVER_ARRAY.put(event, observerList)
}
if (observer !in observerList) {
observerList.add(observer)
}
}
}
@Synchronized
fun unregister(observer: Observer?) {
observer?.listEvents()?.forEach { event ->
OBSERVER_ARRAY.get(event)?.removeLastOccurrence(observer)
}
}
@Synchronized
fun notify(event: Int, vararg args: Any?) {
OBSERVER_ARRAY.get(event)?.forEach { observer ->
HANDLER.post { observer.onEvent(event, *args) }
}
}
}HANDLER dispatches events on the UI thread. OBSERVER_ARRAY maps event IDs to lists of observers, similar to a Map<Integer, LinkedList<Observer>>.
register adds an observer to the lists of its interested events; notify iterates the list for a given event and calls onEvent.
In short, one event can have multiple observers, and one observer can listen to multiple events.
Usage
Example with a simple login/logout scenario:
1. Define Events
object Events {
const val LOGIN = 1
const val LOGOUT = 2
}2. Register/Unregister
abstract class BaseActivity : AppCompatActivity(), Observer {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
EventManager.register(this)
}
override fun onDestroy() {
super.onDestroy()
EventManager.unregister(this)
}
override fun onEvent(event: Int, vararg args: Any?) {}
override fun listEvents(): IntArray = IntArray(0)
}Activities or Fragments can register in onCreate and unregister in onDestroy. Subclasses override listEvents to specify interested events.
3. Subscribe and Handle Callbacks
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// UI setup
}
override fun listEvents(): IntArray = intArrayOf(Events.LOGIN, Events.LOGOUT)
override fun onEvent(event: Int, vararg args: Any?) {
when (event) {
Events.LOGIN -> setViews(args[0] as String?)
Events.LOGOUT -> setViews(null)
}
}
private fun setViews(account: String?) {
// update UI
}
}Since callbacks run on the UI thread, UI updates can be performed directly.
4. Send Events
object AccountManager {
fun login(account: String, password: String) {
// process login
EventManager.notify(Events.LOGIN, account)
}
fun logout() {
// process logout
EventManager.notify(Events.LOGOUT)
}
}Events can be sent with or without additional parameters using vararg for flexibility.
Principle Analysis
The approach resembles the observer pattern but focuses on events rather than observable objects. It follows a publish‑subscribe model where observers subscribe to events, and the manager notifies them when those events occur.
Compared to traditional observer pattern, the event‑listener model emphasizes "things happening" (events) instead of "state changes" of a subject.
Other Matters
Lifecycle
Because the event manager holds static references, observers must unregister before they are garbage‑collected to avoid memory leaks. Static observers need not unregister. Weak references can be used to mitigate leaks:
class WeakObserver(target: Observer) : Observer {
private val reference = WeakReference(target)
private val events: IntArray = target.listEvents()
override fun onEvent(event: Int, vararg args: Any?) {
reference.get()?.onEvent(event, *args)
}
override fun listEvents(): IntArray = events
}Duplicate Check
When many events are defined, duplicate integer values can cause interference. A unit test can verify uniqueness:
fun testDuplicate() {
val fields = Events::class.java.declaredFields
val events = fields.filter { it.type == Int::class.java }
val eventSet = events.map { it.getInt(Events::class.java) }.toSet()
Assert.assertEquals(events.size, eventSet.size)
}If duplicates exist, the test fails (e.g., expected 3 but was 2).
View Events
IDE shortcuts like "Find Usages" help locate all subscribers and senders of a particular event.
Conclusion
The presented lightweight event framework offers clear event management without the extra features of libraries like EventBus (e.g., sticky events, thread selection). It aims to simplify component communication while remaining easy to understand and extend.
Suishouji Tech Team
Suishouji's official tech channel, sharing original technical articles, posting recruitment opportunities, and hosting events. Follow us.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
