Event Bus Alternatives

Earlier in the book, we covered the concept of an event bus as a way of communicating between portions of our app, focusing on one event bus implementation: greenrobot’s EventBus. Later, in the chapter on broadcast Intent objects, we briefly covered LocalBroadcastManager.

However, those are not the only event buses available for Android, and others may fit your needs better. In this chapter, we will explore these and other event bus implementations, to compare and contrast.

Prerequisites

Understanding this chapter requires that you have read the core chapters of this book, particularly the chapters on basic event bus usage, broadcast Intents, AlarmManager and the scheduled service pattern, and Notifications.

A Brief Note About the Sample Apps

The sample apps in this chapter are generally designed to run forever.

It is unlikely that you really want them to run forever, though. Hence, please uninstall each sample after experimenting with it, particularly if you are testing on hardware, such as your personal phone. Your battery will appreciate it.

Standard Intents as Event Bus

You can think of the standard Intent and <intent-filter> system as a three-channel event bus:

The component starting an activity does not need to communicate directly with code for that activity — in fact, often times this is impossible, as they are separate apps running in separate processes. Instead, the component starting an activity sends an event indicating the particular operation to be performed (e.g., view this URL), and Android and the user determine which of candidate consumers is the one to process that event.

However, broadcast Intent objects are a closer analogue to a real “event bus”, in that an event produced by somebody can be consumed by zero, one, or several subscribed consumers, based upon the filtering provided by <intent-filter> elements in the manifest or IntentFilter objects for use with registerReceiver().

In theory, you could use broadcast Intent objects as the backbone for a fairly flexible event bus within your app. In practice, this is not usually a good idea:

However, if you specifically need a cross-process event bus, such as between a suite of related apps, using a broadcast Intent is a very likely choice.

LocalBroadcastManager as Event Bus

As was briefly noted earlier in the book, the Android Support package offers a LocalBroadcastManager. This is designed to offer an event bus with a feel very similar to classic broadcast Intent objects, but local to your process. Not only does this avoid IPC overhead, but it improves security, as other apps have no means of spying on your internal communications.

LocalBroadcastManager is supplied by both the support-v4 and support-v13 libraries. Generally speaking, if your minSdkVersion is less than 13, you probably should choose support-v4.

A Simple LocalBroadcastManager Sample

Let’s see LocalBroadcastManager in action via the Intents/Local sample project.

Here, our LocalActivity sends a command to a NoticeService from onCreate():

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    notice=(TextView)findViewById(R.id.notice);
    startService(new Intent(this, NoticeService.class));
  }
(from Intents/Local/app/src/main/java/com/commonsware/android/localcast/LocalActivity.java)

The NoticeService simply delays five seconds, then sends a local broadcast using LocalBroadcastManager:

package com.commonsware.android.localcast;

import android.app.IntentService;
import android.content.Intent;
import android.os.SystemClock;
import android.support.v4.content.LocalBroadcastManager;

public class NoticeService extends IntentService {
  public static final String BROADCAST=
      "com.commonsware.android.localcast.NoticeService.BROADCAST";
  private static Intent broadcast=new Intent(BROADCAST);

  public NoticeService() {
    super("NoticeService");
  }

  @Override
  protected void onHandleIntent(Intent intent) {
    SystemClock.sleep(5000);
    LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
  }
}
(from Intents/Local/app/src/main/java/com/commonsware/android/localcast/NoticeService.java)

Specifically, you get at your process’ singleton instance of LocalBroadcastManager by calling getInstance() on the LocalBroadcastManager class.

Our LocalActivity registers for this local broadcast in onStart(), once again using getInstance() on LocalBroadcastManager:

  @Override
  public void onStart() {
    super.onStart();

    IntentFilter filter=new IntentFilter(NoticeService.BROADCAST);

    LocalBroadcastManager.getInstance(this).registerReceiver(onNotice,
                                                             filter);
  }
(from Intents/Local/app/src/main/java/com/commonsware/android/localcast/LocalActivity.java)

LocalActivity unregisters for this broadcast in onStop():

  @Override
  public void onStop() {
    super.onStop();

    LocalBroadcastManager.getInstance(this).unregisterReceiver(onNotice);
  }
(from Intents/Local/app/src/main/java/com/commonsware/android/localcast/LocalActivity.java)

The BroadcastReceiver simply updates a TextView with the current date and time:

  private BroadcastReceiver onNotice=new BroadcastReceiver() {
    public void onReceive(Context ctxt, Intent i) {
      notice.setText(new Date().toString());
    }
  };
(from Intents/Local/app/src/main/java/com/commonsware/android/localcast/LocalActivity.java)

If you start up this activity, you will see a “(waiting...)” bit of placeholder text for about five seconds, before having that be replaced by the current date and time.

The BroadcastReceiver, the IntentFilter, and the Intent being broadcast are the same as we would use with full broadcasts. It is merely how we are using them — via LocalBroadcastManager – that dictates they are local to our process versus the standard device-wide broadcasts.

A More Elaborate Sample

That sample is not terribly realistic, but it is simple.

A somewhat more realistic sample is the one using AlarmManager and JobIntentService from elsewhere in the book. However, that app is also fairly unrealistic, at least in terms of its output, as LogCat is not very useful to users. A more typical approach for a background service like this is to notify a foreground Activity, if there is one, about work that was accomplished, and otherwise display a Notification. We described that pattern in the chapter on Notifications.

In the EventBus/LocalBroadcastManager sample project, we blend:

The Activity

The EventDemoActivity that is our app’s entry point is a bit similar to the one used in the AlarmManager demo, in that it calls scheduleAlarms() on PollReceiver to set up the AlarmManager schedule:

package com.commonsware.android.eventbus.lbm;

import android.support.v4.app.FragmentActivity;
import android.os.Bundle;

public class EventDemoActivity extends FragmentActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (getSupportFragmentManager().findFragmentById(android.R.id.content) == null) {
      getSupportFragmentManager().beginTransaction()
                          .add(android.R.id.content,
                               new EventLogFragment()).commit();

      PollReceiver.scheduleAlarms(this);
    }
  }
}
(from EventBus/LocalBroadcastManager/app/src/main/java/com/commonsware/android/eventbus/lbm/EventDemoActivity.java)

However, we also put an EventLogFragment on the screen, if it is not already there, via a FragmentTransaction. This is where we will display events coming from the service, while our activity is in the foreground. We will examine EventLogFragment and how it participates in the event bus shortly.

The PollReceiver

PollReceiver is largely unchanged from its AlarmManager demo original edition. This BroadcastReceiver will be used both for getting control at boot time (to reschedule the alarms, wiped on the reboot) and for sending the work to the ScheduledService for processing:

package com.commonsware.android.eventbus.lbm;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;

public class PollReceiver extends BroadcastReceiver {
  private static final int PERIOD=60000; // 1 minute
  private static final int INITIAL_DELAY=5000; // 5 seconds

  @Override
  public void onReceive(Context ctxt, Intent i) {
    if (i.getAction() == null) {
      ScheduledService.enqueueWork(ctxt);
    }
    else {
      scheduleAlarms(ctxt);
    }
  }

  static void scheduleAlarms(Context ctxt) {
    AlarmManager mgr=
        (AlarmManager)ctxt.getSystemService(Context.ALARM_SERVICE);
    Intent i=new Intent(ctxt, PollReceiver.class);
    PendingIntent pi=PendingIntent.getBroadcast(ctxt, 0, i, 0);

    mgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     SystemClock.elapsedRealtime() + INITIAL_DELAY,
                     PERIOD, pi);

  }
}
(from EventBus/LocalBroadcastManager/app/src/main/java/com/commonsware/android/eventbus/lbm/PollReceiver.java)

ScheduledService and Sending Events

Before, our ScheduledService just dumped a message to LogCat. This was crude but effective for what that demo required. Now, we want our service to let the UI layer know about some work that was accomplished, or to raise a Notification.

In this case, the “work” is generating a random number.

package com.commonsware.android.eventbus.lbm;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.v4.app.JobIntentService;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import java.util.Calendar;
import java.util.Random;

public class ScheduledService extends JobIntentService {
  private static int NOTIFY_ID=1337;
  private static final int UNIQUE_JOB_ID=1337;
  private static final String CHANNEL_WHATEVER="channel_whatever";
  private Random rng=new Random();

  static void enqueueWork(Context ctxt) {
    enqueueWork(ctxt, ScheduledService.class, UNIQUE_JOB_ID,
      new Intent(ctxt, ScheduledService.class));
  }

  @Override
  public void onHandleWork(Intent i) {
    Intent event=new Intent(EventLogFragment.ACTION_EVENT);
    long now=Calendar.getInstance().getTimeInMillis();
    int random=rng.nextInt();

    event.putExtra(EventLogFragment.EXTRA_RANDOM, random);
    event.putExtra(EventLogFragment.EXTRA_TIME, now);

    if (!LocalBroadcastManager.getInstance(this).sendBroadcast(event)) {
      NotificationManager mgr=
        (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

      if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O &&
        mgr.getNotificationChannel(CHANNEL_WHATEVER)==null) {
        mgr.createNotificationChannel(new NotificationChannel(CHANNEL_WHATEVER,
          "Whatever", NotificationManager.IMPORTANCE_DEFAULT));
      }

      NotificationCompat.Builder b=new NotificationCompat.Builder(this, CHANNEL_WHATEVER);
      Intent ui=new Intent(this, EventDemoActivity.class);

      b.setAutoCancel(true).setDefaults(Notification.DEFAULT_SOUND)
       .setContentTitle(getString(R.string.notif_title))
       .setContentText(Integer.toHexString(random))
       .setSmallIcon(android.R.drawable.stat_notify_more)
       .setTicker(getString(R.string.notif_title))
       .setContentIntent(PendingIntent.getActivity(this, 0, ui, 0));
      
      mgr.notify(NOTIFY_ID, b.build());
    }
  }
}
(from EventBus/LocalBroadcastManager/app/src/main/java/com/commonsware/android/eventbus/lbm/ScheduledService.java)

LocalBroadcastManager, as we have seen, uses the same Intent and IntentFilter and BroadcastReceiver structures as are used with regular broadcasts, just via a singleton message bus (LocalBroadcastManager.getInstance()) instead of the framework’s IPC engine. Hence, we need an Intent that represents the message, so we create one, using an action string published by the EventLogFragment. We also attach two extras to this Intent, using keys published by EventLogFragment: the random number, plus the time of this event.

We then call sendBroadcast() on the singleton LocalBroadcastManager. This returns a boolean value, true indicating that one or more locally-registered receivers were delivered the Intent, false otherwise. Hence, if sendBroadcast() returns true, we can assume that somebody in the UI layer picked up our message and is now responsible for displaying these results to the user.

Conversely, if sendBroadcast() returns false, we must assume that the UI layer did not receive the message, and so the service should inform the user directly, in this case via a Notification, showing the random number as the text in the notification drawer.

EventLogFragment and Receiving Events

EventLogFragment, therefore, is responsible for:

In this case, we use a retained ListFragment with a ListView set into transcript mode, meaning that entries are added at the bottom, and older entries scroll off the top, like a chat transcript:

package com.commonsware.android.eventbus.lbm;

import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ListFragment;
import android.support.v4.content.LocalBroadcastManager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;

public class EventLogFragment extends ListFragment {
  static final String EXTRA_RANDOM="r";
  static final String EXTRA_TIME="t";
  static final String ACTION_EVENT="e";
  private EventLogAdapter adapter=null;

  @Override
  public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setRetainInstance(true);
  }

  @Override
  public void onViewCreated(@NonNull View view,
                            @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    getListView().setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);

    if (adapter == null) {
      adapter=new EventLogAdapter();
    }

    setListAdapter(adapter);
  }

  @Override
  public void onStart() {
    super.onStart();

    IntentFilter filter=new IntentFilter(ACTION_EVENT);

    LocalBroadcastManager.getInstance(getActivity())
                         .registerReceiver(onEvent, filter);
  }

  @Override
  public void onStop() {
    LocalBroadcastManager.getInstance(getActivity())
                         .unregisterReceiver(onEvent);

    super.onStop();
  }

  class EventLogAdapter extends ArrayAdapter<Intent> {
    DateFormat fmt=new SimpleDateFormat("HH:mm:ss", Locale.US);

    public EventLogAdapter() {
      super(getActivity(), android.R.layout.simple_list_item_1,
            new ArrayList<Intent>());
    }

    @SuppressLint("DefaultLocale")
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      TextView row=
          (TextView)super.getView(position, convertView, parent);
      Intent event=getItem(position);
      Date date=new Date(event.getLongExtra(EXTRA_TIME, 0));

      row.setText(String.format("%s = %x", fmt.format(date),
                                event.getIntExtra(EXTRA_RANDOM, -1)));

      return(row);
    }
  }

  private BroadcastReceiver onEvent=new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      adapter.add(intent);
    }
  };
}
(from EventBus/LocalBroadcastManager/app/src/main/java/com/commonsware/android/eventbus/lbm/EventLogFragment.java)

The ListAdapter for the ListView is an EventLogAdapter, an ArrayAdapter for Intent objects, where in getView() we populate the list rows with the time and random value.

In onStart() and onStop(), we register for (and unregister from) the desired broadcast, pointing to an onEvent BroadcastReceiver that adds the incoming Intent to the EventLogAdapter. That, in turn, updates the ListView.

The result is that while the activity is in the foreground, the events will be displayed to the user directly:

LocalBroadcastManager as Event Bus, Demo Activity
Figure 646: LocalBroadcastManager as Event Bus, Demo Activity

Whereas if events are processed while the activity is not in the foreground, a Notification will be shown with the last results:

LocalBroadcastManager as Event Bus, Demo Notification
Figure 647: LocalBroadcastManager as Event Bus, Demo Notification

Reference, Not Value

When you send a “real” broadcast Intent, your Intent is converted into a byte array (courtesy of the Parcelable interface) and transmitted to other processes. This occurs even if the recipient of the Intent is within your own process — that is what makes LocalBroadcastManager faster, as it avoids the inter-process communication.

However, since LocalBroadcastManager does not need to send your Intent between processes, that means it does not turn your Intent into a byte array. Instead, it just passes the Intent along to any registered BroadcastReceiver with a matching IntentFilter. In effect, while “real” broadcasts are pass-by-value, local broadcasts are pass-by-reference.

This can have subtle side effects.

For example, there are a few ways that you can put a collection into an Intent extra, such as putStringArrayListExtra(). This takes an ArrayList as a parameter. With a real broadcast, once you send the broadcast, it does not matter what happens to the original ArrayList — the rest of the system is working off of a copy. With a local broadcast, though, the Intent holds onto the ArrayList you supplied via the setter. If you change that ArrayList elsewhere (e.g., clear it for reuse), the recipient of the Intent will see those changes.

Similarly, if you put a Parcelable object in an extra, the Intent holds onto the actual object while it is being broadcast locally, whereas a real broadcast would have resulted in a copy. If you change the object while the broadcast is in progress, the recipient of the broadcast will see those changes.

This can be a feature, not a bug, when used properly. But, regardless, it is a non-trivial difference, one that you will need to keep in mind.

Limitations of Local

While LocalBroadcastManager is certainly useful, it has some serious limitations.

The biggest is that it is purely local. While traditional broadcasts can either be internal (via setPackage()) or device-wide, LocalBroadcastManager only handles the local case. Hence, anything that might involve other processes, such as a PendingIntent, will not use LocalBroadcastManager. For example, you cannot register a receiver through LocalBroadcastManager, then use a getBroadcast() PendingIntent to try to reach that BroadcastReceiver. The PendingIntent will use the regular broadcast Intent mechanism, which the local-only receiver will not respond to.

Similarly, since a manifest-registered BroadcastReceiver is spawned via the operating system upon receipt of a matching true broadcast, you cannot use such receivers with LocalBroadcastManager. Only a BroadcastReceiver registered via registerReceiver() on the LocalBroadcastManager will use the LocalBroadcastManager.

Also, LocalBroadcastManager does not offer ordered or sticky broadcasts.

greenrobot’s EventBus 3.x

LocalBroadcastManager has two major advantages:

  1. It is part of the Android Support package, and therefore it is part of the officially-supported corner of the Android ecosystem
  2. It works like traditional broadcasts, which will make it easier for some developers to “wrap their heads around” it

However, that same dependency on the Intent and IntentFilter structure adds bulk and limits flexibility. Hence, it is not surprising that there are alternative event buses to LocalBroadcastManager.

Java, outside of Android, has had a few event bus implementations. One of the more popular ones in recent years has been the event bus that is part of Google’s Guava family of libraries. However, while a Java event bus perhaps can be used on Android, it may not be optimal for Android. Hence, a few projects have started with Guava’s event bus implementation and have extended it to be a bit more Android-aware, or perhaps even Android-centric.

greenrobot’s EventBus is one such event bus.

NOTE: For the purposes of this chapter, “greenrobot’s EventBus” refers to the library, and “EventBus”" refers to the EventBus Java class in that library.

Basic Usage and Sample App

With LocalBroadcastManager, you work with a singleton instance, calling methods like registerReceiver() and sendBroadcast() upon it to subscribe to and raise events, respectively.

With greenrobot’s EventBus, you work with an EventBus instance, calling methods like register() and post() upon it to subscribe to and raise events, respectively. Usually, we use the singleton instance of EventBus that we get by calling getDefault() on the EventBus class, but you are welcome to have different EventBus objects, representing distinct communications channels, if you wish.

Hence, at the core, greenrobot’s EventBus behaves much like LocalBroadcastManager. What differs is in the nature of the events and the subscribers.

With LocalBroadcastManager, events are Intent objects. With greenrobot’s EventBus, an event can be whatever data type you like. Hence, you can create your own ...Event classes, holding whatever bits of data, in whatever data types suit you — you are not restricted to things that can go in an Intent extra. However, as has been noted on occasion, “with great power comes great responsibility”, and so you will need to ensure that you use this carefully and do not wind up creating some sort of memory leak as a result. For example, do not pass something from an Activity to a Service via a custom event, where the Service will hold onto that information for a long time, if that “something” holds a reference back to the Activity.

With LocalBroadcastManager, subscribers are BroadcastReceivers, who use an IntentFilter to identify which events they are interested in. With greenrobot’s EventBus, subscribers are any class you want. A special @Subscribe annotation is used to both indicate what sorts of events the subscriber is interested in (based on the parameter to the annotated method) and what method should be invoked when a matching event is raised (the annotated method itself). Hence, not only do you use custom event classes to allow you to carry along custom data, but you use them as a filtering mechanism, much like you would use custom action strings with LocalBroadcastManager.

To see how this works, take a look at the EventBus/GreenRobot3 sample project, which is a clone of the EventBus/LocalBroadcastManager demo, but one where we substitute in greenrobot’s EventBus as a replacement for LocalBroadcastManager. Our activity and PollReceiver are unchanged: they did not directly interact with LocalBroadcastManager and do not need to interact with greenrobot’s EventBus. The changes are isolated in our ScheduledService and EventLogFragment.

ScheduledService and Sending Events

We will need an EventBus instance, one that serves the same basic role as does the singleton LocalBroadcastManager retrieved by getInstance(). As noted above, you can call getDefault() on EventBus to get a singleton EventBus instance, and this suffices in most cases.

When it comes time for us to send a message, we can call post() on the EventBus, supplying whatever sort of event object that we want:

      EventBus.getDefault().post(randomEvent);
(from EventBus/GreenRobot3/app/src/main/java/com/commonsware/android/eventbus/greenrobot/ScheduledService.java)

Here, we are posting an instance of a RandomEvent:

package com.commonsware.android.eventbus.greenrobot;

import java.util.Calendar;
import java.util.Date;

public class RandomEvent {
  Date when=Calendar.getInstance().getTime();
  int value;
  
  RandomEvent(int value) {
    this.value=value;
  }
}
(from EventBus/GreenRobot3/app/src/main/java/com/commonsware/android/eventbus/greenrobot/RandomEvent.java)

EventLogFragment and Receiving Events

Over in our EventLogFragment, rather than register and unregister a BroadcastReceiver in onStart() and onStop(), we register and unregister the fragment itself with the EventBus:

  @Override
  public void onStart() {
    super.onStart();

    EventBus.getDefault().register(this);
  }

  @Override
  public void onStop() {
    EventBus.getDefault().unregister(this);

    super.onStop();
  }
(from EventBus/GreenRobot3/app/src/main/java/com/commonsware/android/eventbus/greenrobot/EventLogFragment.java)

Now, we can use the @Subscribe annotation to arrange to receive any event we want that is delivered via this EventBus, based on event class. Since we want to receive RandomEvent messages, we merely need to have a public void method, taking a RandomEvent parameter, marked with the @Subscribe annotation, such as onRandomEvent():

  @Subscribe(threadMode = ThreadMode.MAIN)
  public void onRandomEvent(final RandomEvent event) {
    adapter.add(event);
  }
(from EventBus/GreenRobot3/app/src/main/java/com/commonsware/android/eventbus/greenrobot/EventLogFragment.java)

Note that the method name can be anything we want, as it is the annotation, not the method name, that identifies this as being an event handling method.

Since Java annotations can take key-value pairs for configuration, EventBus 3.x uses that to configure the behavior of @Subscribe. Here, we use @Subscribe(threadMode = ThreadMode.MAIN), to indicate that we want this event to be delivered to this method on the main application thread.

In this method, we can do what we need to with our RandomEvent. In our case, EventLogAdapter has been modified to be an ArrayAdapter of RandomEvent, as opposed to being an ArrayAdapter of Intent as in the earlier sample. What we want to do is append the new RandomEvent to the end of the adapter.

Handling the “Nobody’s Home” Scenario

What is missing, though, is the logic we used in LocalBroadcastManager to determine if somebody received our message, where we raised a Notification if that is not the case.

The solution for this with greenrobot’s EventBus is to call hasSubscriberForEvent(), with the Java Class object of the event that we would like to post(). If this returns true, we have a current subscriber; otherwise, we do not.

So, the full onHandleWork() implementation uses hasSubscriberForEvent() and either uses post() to raise the event or displays a Notification itself:

  @Override
  public void onHandleWork(Intent i) {
    RandomEvent randomEvent=new RandomEvent(rng.nextInt());

    if (EventBus.getDefault().hasSubscriberForEvent(randomEvent.getClass())) {
      EventBus.getDefault().post(randomEvent);
    }
    else {
      NotificationManager mgr=
        (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

      if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O &&
        mgr.getNotificationChannel(CHANNEL_WHATEVER)==null) {
        mgr.createNotificationChannel(new NotificationChannel(CHANNEL_WHATEVER,
          "Whatever", NotificationManager.IMPORTANCE_DEFAULT));
      }

      NotificationCompat.Builder b=new NotificationCompat.Builder(this, CHANNEL_WHATEVER);
      Intent ui=new Intent(this, EventDemoActivity.class);

      b.setAutoCancel(true).setDefaults(Notification.DEFAULT_SOUND)
        .setContentTitle(getString(R.string.notif_title))
        .setContentText(Integer.toHexString(randomEvent.value))
        .setSmallIcon(android.R.drawable.stat_notify_more)
        .setTicker(getString(R.string.notif_title))
        .setContentIntent(PendingIntent.getActivity(this, 0, ui, 0));

      mgr.notify(NOTIFY_ID, b.build());
    }
  }
(from EventBus/GreenRobot3/app/src/main/java/com/commonsware/android/eventbus/greenrobot/ScheduledService.java)

There is a race condition, though. Since our hasSubscriberForEvent() call is happening on a background thread, it is possible that between the hasSubscriberForEvent() call and the post() call that the subscriber unsubscribes. This is an unlikely occurrence here, but it is worth keeping in mind.

Other Notable Capabilities

In addition to the threading features, greenrobot’s EventBus has a few other noteworthy bells and whistles:

Hey, What About Otto?

For a few years, a third major event bus implementation was popular: Square’s Otto. Like greenrobot’s EventBus, Otto was based off of Guava’s EventBus class and was tuned towards Android app development. It shared some characteristics with greenrobot’s EventBus, owing to the shared heritage. On the whole, greenrobot’s EventBus was a bit more complex to use but offered greater flexibility.

Square has since discontinued work on Otto, so unless you have existing legacy code that uses Otto, you should use some other event bus implementation.