Broadcasts and Broadcast Receivers

One channel of the Intent message bus is used to start activities. A second channel of the Intent message bus is used to send broadcasts. As the name suggests, a broadcast Intent is one that — by default – is published to any and all applications on the device that wish to tune in.

Sending a Simple Broadcast

The simplest way to send a broadcast Intent is to create the Intent you want, then call sendBroadcast().

That’s it.

At that point, Android will scan through everything set up to tune into a broadcast matching your Intent, typically filtering just on the action string. Anyone set up to receive this broadcast will, indeed, receive it, using a BroadcastReceiver.

Receiving a Broadcast: In an Activity

To receive such a broadcast in an activity (or a fragment), you will need to do four things.

First, you will need to create an instance of your own subclass of BroadcastReceiver. The only method you need to (or should) implement is onReceive(), which will be passed the Intent that was broadcast, along with a Context object that, in this case, you will typically ignore.

Second, you will need to create an instance of an IntentFilter object, describing the sorts of broadcasts you want to receive. Most of these filters are set up to watch for a single broadcast Intent action, in which case the simple constructor suffices:


new IntentFilter(Intent.ACTION_CAMERA_BUTTON)

Third, you will need to call registerReceiver(), typically from onResume() of your activity or fragment, supplying your BroadcastReceiver and your IntentFilter.

Fourth, you will need to call unregisterReceiver(), typically from onPause() of your activity or fragment, supplying the same BroadcastReceiver instance you provided to registerReceiver().

In between the calls to registerReceiver() and unregisterReceiver(), you will receive any broadcasts matching the IntentFilter.

The biggest downside to this approach is that some activity has to register the receiver. Sometimes, you want to receive broadcasts even when there is no activity around. To do that, you will need to use a different technique: registering the receiver in the manifest.

Receiving a Broadcast: Via the Manifest

You can also tell Android about broadcasts you wish to receive by adding a <receiver> element to your manifest, identifying the class that implements your BroadcastReceiver (via the android:name attribute), plus an <intent-filter> that describes the broadcast(s) you wish to receive:


<receiver android:name=".OnBootReceiver">
  <intent-filter>
    <action android:name="android.intent.action.BOOT_COMPLETED"/>
  </intent-filter>
</receiver>

The good news is that this BroadcastReceiver will be available for broadcasts occurring at any time. There is no assumption that you have an activity already running that called registerReceiver().

The bad news is that the instance of the BroadcastReceiver used by Android to process a broadcast will live for only so long as it takes to execute the onReceive() method. At that point, the BroadcastReceiver is discarded. Hence, it is not safe for a manifest-registered BroadcastReceiver to do anything that needs to run after onReceive() itself completes, such as forking a thread. After all, Android may well terminate the process within milliseconds, if there is no other running component in the process.

More bad news: onReceive() is called on the main application thread — the same main application thread that handles the UI of all of your activities. And, you are subject to the same limitations as are your activity lifecycle methods and anything else called on the main application thread:

This makes using a manifest-registered BroadcastReceiver a bit tricky. If the work to be done is very quick, just implement it in onReceive(). Otherwise, you will probably need to pair this BroadcastReceiver with a component known as an IntentService, which we will examine in the next chapter.

The Stopped State

On Android 3.1 and higher, when your app is first installed on the device, it is in a “stopped” state. This has nothing to do with onStop() of any activity. While in the stopped state, your manifest-registered BroadcastReceivers will not receive any broadcasts.

Getting Out of the Stopped State

To get out of the stopped state, something on the device, such as another app (that itself is not in the stopped state), must use an explicit Intent to invoke one of your components.

The most common way this happens is for the user to tap on a launcher icon associated with your launcher activity. Under the covers, the home screen’s launcher will create an explicit Intent, identifying your activity, and use that with startActivity(). This moves you out of the stopped state.

Getting Into the Stopped State

As noted above, you start off in the stopped state. Once you are moved out of the stopped state, via the explicit Intent, you will remain out of the stopped state until one of two things happens:

  1. The user uninstalls your app
  2. The user “force-stops” your app

The latter normally occurs when the user clicks the “Force Stop” button on your app’s screen in the Settings app (Settings > Apps). There is some evidence that some device manufacturers have tied their own device’s task manager to do a “force stop” when the user removes a task — this was not a particularly wise choice on the part of those manufacturers.

Note that a reboot does not move you back into the stopped state. You remain in the normal state through a reboot.

Example System Broadcasts

There are many, many broadcasts sent out by Android itself, which you can tune into if you see fit. Many, but not all, of these are documented on the Intent class. The values in the “Constants” table that have “Broadcast Action” leading off their description are action strings used for system broadcasts. There are other such broadcast actions scattered around the SDK, though, so do not assume that they are all documented on Intent.

The following sections will examine two of these broadcasts, to see how the BroadcastReceiver works in action.

At Boot Time

A popular request is to have code get control when the device is powered on. This is doable but somewhat dangerous, in that too many on-boot requests slow down the device startup and may make things sluggish for the user.

In order to be notified when the device has completed its system boot process, you will need to request the RECEIVE_BOOT_COMPLETED permission. Without this, even if you arrange to receive the boot broadcast Intent, it will not be dispatched to your receiver.

As the Android documentation describes it:

Though holding this permission does not have any security implications, it can have a negative impact on the user experience by increasing the amount of time it takes the system to start and allowing applications to have themselves running without the user being aware of them. As such, you must explicitly declare your use of this facility to make that visible to the user.

We also need to register our BroadcastReceiver in the manifest — by the time an activity would call registerReceiver(), the boot will have long since occurred.

For example, let us examine the Intents/OnBoot sample project.

In our manifest, we request the needed permission and register our BroadcastReceiver, along with an activity:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.commonsware.android.sysevents.boot"
  android:versionCode="1"
  android:versionName="1.0">

  <uses-sdk
    android:minSdkVersion="7"
    android:targetSdkVersion="11"/>

  <supports-screens
    android:largeScreens="false"
    android:normalScreens="true"
    android:smallScreens="false"/>

  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

  <application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name">
    <receiver android:name=".OnBootReceiver">
      <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
      </intent-filter>
    </receiver>

    <activity
      android:name="BootstrapActivity"
      android:theme="@android:style/Theme.Translucent.NoTitleBar">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
  </application>

</manifest>
(from Intents/OnBoot/app/src/main/AndroidManifest.xml)

OnBootReceiver simply logs a message to LogCat:

package com.commonsware.android.sysevents.boot;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class OnBootReceiver extends BroadcastReceiver {
  @Override
  public void onReceive(Context context, Intent intent) {
    Log.d(getClass().getSimpleName(), "Hi, Mom!");
  }
}
(from Intents/OnBoot/app/src/main/java/com/commonsware/android/sysevents/boot/OnBootReceiver.java)

To test this on Android 3.0 and earlier, simply install the application and reboot the device — you will see the message appear in LogCat.

However, on Android 3.1 and higher, the user must first manually launch some activity before any manifest-registered BroadcastReceiver objects will be used, as noted above in the section covering the stopped state. Hence, if you were to just install the application and reboot the device, nothing would happen. The little BootstrapActivity is merely there for the user to launch, so that the ACTION_BOOT_COMPLETED BroadcastReceiver will start working.

On Battery State Changes

One theme with system events is to use them to help make your users happier by reducing your impacts on the device while the device is not in a great state. Most applications are impacted by battery life. Dead batteries run no apps. Hence, knowing the battery level may be important for your app.

There is an ACTION_BATTERY_CHANGED Intent that gets broadcast as the battery status changes, both in terms of charge (e.g., 80% charged) and charging (e.g., the device is now plugged into AC power). You simply need to register to receive this Intent when it is broadcast, then take appropriate steps.

One of the limitations of ACTION_BATTERY_CHANGED is that you have to use registerReceiver() to set up a BroadcastReceiver to get this Intent when broadcast. You cannot use a manifest-declared receiver. There are separate ACTION_BATTERY_LOW and ACTION_BATTERY_OK broadcasts that you can receive from a manifest-registered receiver, but they are broadcast far less frequently, only when the battery level falls below or rises above some undocumented “low” threshold.

To demonstrate ACTION_BATTERY_CHANGED, take a peek at the Intents/OnBattery sample project.

In there, you will find a res/layout/batt.xml resource containing a ProgressBar, a TextView, and an ImageView, to serve as a battery monitor:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">

  <ProgressBar
    android:id="@+id/bar"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
      android:id="@+id/level"
      android:layout_width="0px"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:textSize="36sp"/>

    <ImageView
      android:id="@+id/status"
      android:layout_width="0px"
      android:layout_height="wrap_content"
      android:layout_weight="1"/>
  </LinearLayout>

</LinearLayout>
(from Intents/OnBattery/app/src/main/res/layout/batt.xml)

This layout is used by a BatteryFragment, which registers to receive the ACTION_BATTERY_CHANGED Intent in onResume() and unregisters in onPause():

package com.commonsware.android.battmon;

import android.app.Fragment;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;

public class BatteryFragment extends Fragment {
  private ProgressBar bar=null;
  private ImageView status=null;
  private TextView level=null;
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup parent,
                           Bundle savedInstanceState) {
    View result=inflater.inflate(R.layout.batt, parent, false);

    bar=(ProgressBar)result.findViewById(R.id.bar);
    status=(ImageView)result.findViewById(R.id.status);
    level=(TextView)result.findViewById(R.id.level);

    return(result);
  }

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

    IntentFilter f=new IntentFilter(Intent.ACTION_BATTERY_CHANGED);

    getActivity().registerReceiver(onBattery, f);
  }

  @Override
  public void onPause() {
    getActivity().unregisterReceiver(onBattery);

    super.onPause();
  }

  BroadcastReceiver onBattery=new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
      int pct=
          100 * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 1)
              / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);

      bar.setProgress(pct);
      level.setText(String.valueOf(pct));

      switch (intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)) {
        case BatteryManager.BATTERY_STATUS_CHARGING:
          status.setImageResource(R.drawable.charging);
          break;

        case BatteryManager.BATTERY_STATUS_FULL:
          int plugged=
              intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);

          if (plugged == BatteryManager.BATTERY_PLUGGED_AC
              || plugged == BatteryManager.BATTERY_PLUGGED_USB) {
            status.setImageResource(R.drawable.full);
          }
          else {
            status.setImageResource(R.drawable.unplugged);
          }
          break;

        default:
          status.setImageResource(R.drawable.unplugged);
          break;
      }
    }
  };
}
(from Intents/OnBattery/app/src/main/java/com/commonsware/android/battmon/BatteryFragment.java)

The key to ACTION_BATTERY_CHANGED is in the “extras”. Many extras are packaged in the Intent, to describe the current state of the battery, such as the following constants defined on the BatteryManager class:

In the case of BatteryFragment, when we receive an ACTION_BATTERY_CHANGED Intent, we do three things:

  1. We compute the percentage of battery life remaining, by dividing the level by the scale
  2. We update the ProgressBar and TextView to display the battery life as a percentage
  3. We display an icon, with the icon selection depending on whether we are charging (status is BATTERY_STATUS_CHARGING), full but on the charger (status is BATTERY_STATUS_FULL and plugged is BATTERY_PLUGGED_AC or BATTERY_PLUGGED_USB), or are not plugged in

If you plug this into a device, it will show you the device’s charge level:

The Battery Monitor
Figure 320: The Battery Monitor

Sticky Broadcasts and the Battery

NOTE: Sticky broadcasts are deprecated in Android 5.0, and the documentation hints that they may be abandoned entirely in the future.

Android has a notion of “sticky broadcast Intents”. Normally, a broadcast Intent will be delivered to interested parties and then discarded. A sticky broadcast Intent is delivered to interested parties and retained until the next matching Intent is broadcast. Applications can call registerReceiver() with an IntentFilter that matches the sticky broadcast, but with a null BroadcastReceiver, and get the sticky Intent back as a result of the registerReceiver() call.

This may sound confusing. Let’s look at this in the context of the battery.

Earlier in this section, you saw how to register for ACTION_BATTERY_CHANGED to get information about the battery delivered to you. You can also, though, get the latest battery information without registering a receiver. Just create an IntentFilter to match ACTION_BATTERY_CHANGED (as shown above) and call registerReceiver() with that filter and a null BroadcastReceiver. The Intent you get back from registerReceiver() is the last ACTION_BATTERY_CHANGED Intent that was broadcast, with the same extras. Hence, you can use this to get the current (or near-current) battery status, rather than having to bother registering an actual BroadcastReceiver.

This is why the sample app shows its results immediately — it was given the last-broadcast edition of the ACTION_BATTERY_CHANGED broadcast once we called registerReceiver().

Battery and the Emulator

Your emulator does not really have a battery. If you run this sample application on an emulator, you will see, by default, that your device has 50% fake charge remaining and that it is being charged. However, it is charged infinitely slowly, as it will not climb past 50%… at least, not without help.

NOTE: At the time of this writing, the Linux emulator does not properly emulate the battery for AVDs created from certain device profiles (e.g., Nexus S), showing 0% battery charge and not responding to the telnet commands described below. As is noted in this issue, if you encounter this, go into the config.ini file for your AVD (found in ~/.android/avd/.../, where ~/ is your home directory and ... is the name of the AVD) and add hw.battery=yes as a property. If that property exists but is set to no, change it to yes.

While the emulator will only show fixed battery characteristics, you can change what those values are, through the highly advanced user interface known as telnet.

You may have noticed that your emulator title bar consists of the name of your AVD plus a number, frequently 5554. That number is not merely some engineer’s favorite number. It is also an open port, on your emulator, to which you can telnet into, on localhost (127.0.0.1) on your development machine.

There are many commands you can issue to the emulator by means of telnet . To change the battery level, use power capacity NN, where NN is the percentage of battery life remaining that you wish the emulator to return. If you do that while you have an ACTION_BATTERY_CHANGED BroadcastReceiver registered, the receiver will receive a broadcast Intent, informing you of the change.

You can also experiment with some of the other power subcommands (e.g., power ac on or power ac off), or other commands (e.g., geo, to send simulated GPS fixes, just as you can do from DDMS).

Battery Data on Android 5.0+

As noted earlier, Android 5.0 deprecates sticky broadcasts. The existing broadcasts still work, though. And, even if someday Android gets rid of sticky broadcasts entirely, broadcasts like ACTION_BATTERY_CHANGED most likely will still work, albeit just as a regular broadcast.

To get current battery information on Android 5.0 and higher, BatteryManager offers getIntProperty() and getLongProperty(), where the keys for the “properties” are BATTERY_PROPERTY_* constants defined on BatteryManager, such as BATTERY_PROPERTY_CAPACITY to determine the percentage of remaining battery capacity.

The Order of Things

Another variation on the broadcast Intent is the ordered broadcast.

Normally, if you broadcast an Intent, and there are 10 registered BroadcastReceivers that match that Intent, all 10 will receive the broadcast, in indeterminate order, and possibly in parallel (particularly on multi-core devices).

With an ordered broadcast, the behavior shifts a bit:

Sending an ordered broadcast is merely a matter of calling sendOrderedBroadcast().

Receiving an ordered broadcast, at its core, is identical to receiving a regular broadcast: you write a BroadcastReceiver and register it via the manifest or registerReceiver(). However, you have two additional options when registering that BroadcastReceiver.

First, you can specify a priority, either via setPriority() on the IntentFilter or android:priority on the <intent-filter> element. The priority is a positive integer, with higher numbers indicating higher priority. Higher-priority receivers will get the broadcast sooner than will lower-priority receivers.

Second, your BroadcastReceiver can call abortBroadcast() to consume the event, preventing any lower-priority receivers from even seeing the broadcast.

Keeping It Local

A broadcast Intent, by default and nearly by definition, is broadcast. Anything on the device could have a receiver “tuned in” to listen for such broadcasts. While you can use setPackage() on Intent to restrict the distribution, the broadcast still goes through the standard broadcast mechanism, which involves transferring the Intent to an OS process, which then does the actual broadcasting. Hence, a broadcast Intent has some overhead.

Yet, there are times when using broadcasts within an app is handy, but it would be nice to avoid the overhead. To help with this the core Android team added LocalBroadcastManager to the Android Support package, to provide an in-process way of doing broadcasts with the standard Intent, IntentFilter, and BroadcastReceiver classes, yet with less overhead.

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

The only real difference, from a coding standpoint, in using LocalBroadcastManager is that you call registerReceiver(), unregisterReceiver(), and sendBroadcast() on an instance of LocalBroadcastManager, instead of on an instance of Context. You get the LocalBroadcastManager singleton for your process via a static getInstance() method on LocalBroadcastManager itself.

We will see LocalBroadcastManager in use in one of the samples in the services chapter.

Visit the Trails!

We examine LocalBroadcastManager in more detail, along with other event bus alternatives, later in the book.