Android Programming: Pushing the Limits (2014)
Part II. Getting the Most Out of Components
Chapter 8. Mastering BroadcastReceivers and Configuration Changes
An Android-based smartphone is a very powerful device with lots of different hardware components. Many of
these components affect the state of the smartphone in different ways. The accelerometer detects the current
physical orientation of the device (portrait or landscape), the Wi-Fi discovers new available networks and
notifies the system when its connection to a network changes, the light-sensor controls the brightness of the
screen, and the hardware buttons on the device trigger interrupts that generate some event in the system.
At the same time, the Android system always tries to consume as little power as possible in order to make
the battery last as long as possible. However, because Android applications have a relatively high degree of
freedom as to how much they can control the device, it’s very important for you, as a developer, to react to the
different events and changes to the device; otherwise, they could cause unnecessary power drain. By mastering
the different system events and device changes, you can make your application more robust and work more
smoothly with the overall system. A badly written application that eats up a user’s batteries will receive bad
reviews, and ultimately users will seek better alternatives.
Also, most of these events are not directly related to battery consumption or performance. However, you need
to be aware of them in order to anticipate changes that will affect how your device is working. For instance,
if the device loses its Wi-Fi connection and switches to the much slower EDGE connection, you may want to
reduce the number of network calls or pick a smaller version of pictures you’re downloading. By watching for
events indicating if the screen changes from on to off (or vice versa), you can make additional assumptions
about a user’s activity and take appropriate actions based on that (for instance, screen off would probably mean
that the user isn’t actively looking at the device).
As an application developer, you can also send broadcast, either defined by the application as a new Intent
action or by using some action defined by the Android APIs or third-party application. Doing so can be very
useful when you want to dispatch events in the background, either within your own application or between
two different applications.
Although an application can listen to numerous events, I cover only a few of them in this chapter
because there are so many defined by the Android platform. For more information about broadcast
events, go to the official Android documentation site: http://d.android.com, and check out the
relevant APIs. For instance, events related to telephony features are usually found in the android.
telephony package.
There are also different ways to listen for these events. Some are sent as broadcast Intents and received
by BroadcastReceivers; others require you to implement some Java callback. In this chapter, I provide
examples of these methods and how you can efficiently implement them.
BroadcastReceivers
The most common way that events are broadcast on Android is through Intent objects sent to
BroadcastReceivers using the Context.sendBroadcast() methods. Many of the standard system
events are defined as action strings and can be found in the API documentation for the Intent class. For
instance, if your application needs to be notified whenever the user connects or disconnects the charger to the
smartphone, two broadcast actions defined in the Intent class do that: ACTION_POWER_DISCONNECTED
and ACTION_POWER_CONNECTED.
The following code shows a simple BroadcastReceiver that receives an Intent object whenever the user
connects or disconnects the power. In this method, the only thing you do is call Context.startService()
to delegate the event to a service that performs the actual work.
public class ChargerConnectedListener extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
context.startService(
new Intent(MyService.ACTION_POWER_CONNECTED));
} else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
context.startService(
new Intent(MyService.ACTION_POWER_DISCONNECTED));
}
}
}
The default method for implementing BroadcastReceivers is to declare them in the manifest.
Consequently, it’s possible for the BroadcastReceiver to notify your service even though the user hasn’t
started your application. This factor is especially useful for applications that should start on certain system
events without user interaction. You could use the following approach to be notified whenever connectivity to
Wi-Fi changes. For example, when the user comes home and connects her device to the home Wi-Fi, you could
design your application so that it starts to sync data with other devices she has connected to the same network,
without her having to manually trigger this action.
<receiver android:name=”.ChargerConnectedListener”>
<intent-filter>
<action
android:name=”android.intent.action.ACTION_POWER_CONNECTED” />
<action
android:name=”android.intent.action.ACTION_POWER_DISCONNECTED” />
</intent-filter>
</receiver>
BroadcastReceivers can also be registered programmatically within Activities and Services. Some
broadcast Intents can only be registered programmatically, and some only work if you declare them in your
manifest. Check the official Android API documentation for details on each action.
When you register a BroadcastReceiver programmatically, as shown in the following example, you must
also unregister it in the matching callback. In the this example, the receiver is registered for the two actions in
onResume() therefore, you unregister it in onPause().
public class MyActivity extends Activity {
private ChargerConnectedListener mPowerConnectionReceiver;
@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_POWER_CONNECTED);
intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
mPowerConnectionReceiver = new ChargerConnecedListener();
registerReceiver(mPowerConnectionReceiver, intentFilter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mPowerConnectionReceiver);
}
}
You’ll usually want to programmatically register BroadcastReceivers when you’re only interested in the
events while your application is running and active. This way, your application will consume fewer resources
than if you declare them in your manifest and they start every time the event occurs. When your application
needs to receive system events, always consider the difference between a BroadcastReceiver registered in
the manifest or through Context.registerReceiver() so that you avoid using any more resources than
necessary.
Local BroadcastReceivers
If you want to send and receive broadcast only within your own application’s process, consider using the
LocalBroadcastManager instead of the more generic Context.sendBroadcast() method.
This approach is more efficient because no cross-process management is included and you don’t have to
consider the security issues normally involved with broadcasts. This class is not part of the standard APIs but
is contained in the support APIs. The following code exemplifies how to send a local broadcast using the
LocalBroadcastManager.
public void sendLocalBroadcast(Intent broadcastIntent) {
LocalBroadcastManager localBroadcastManager =
LocalBroadcastManager.getInstance(this);
localBroadcastManager.sendBroadcast(broadcastIntent);
}
To receive a local broadcast, you use the LocalBroadcastManager shown in the preceding code. The following
example shows a simple Activity where you register and unregister for local broadcasts for a certain action.
public class LocalBroadcastDemo extends Activity {
public static final String LOCAL_BRODCAST_ACTION = “localBroadcast”;
private BroadcastReceiver mLocalReceiver;
@Override
protected void onResume() {
super.onResume();
LocalBroadcastManager localBroadcastManager =
LocalBroadcastManager.getInstance(this);
IntentFilter intentFilter = new IntentFilter(LOCAL_BRODCAST_
ACTION);
mLocalReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Handle local broadcast...
}
};
localBroadcastManager.registerReceiver(mLocalReceiver,
intentFilter);
}
@Override
protected void onPause() {
super.onPause();
LocalBroadcastManager localBroadcastManager =
LocalBroadcastManager.getInstance(this);
localBroadcastManager.unregisterReceiver(mLocalReceiver);
}
}
Local broadcast is a convenient way of broadcasting messages and states within your application. Local
broadcast is more efficient than standard global broadcasts and is more secure because you can be certain that
no data sent this way will leak outside your application. Remember to always unregister these local receivers in
the same way you do normal receivers, or you may leak memory.
Normal and Ordered Broadcasts
Broadcasts are divided into two categories, normal and ordered. The normal broadcasts are sent to all receivers
asynchronously and are received in an unspecified order, as shown in Figure 8-1. This method is more efficient
but lacks some of the advanced features found for ordered broadcasts. No feedback can be sent to the
broadcaster with normal broadcasts.
An ordered broadcast is delivered to the registered receivers one at a time in a specified order (see Figure 8-2).
You can control the order in which the broadcasts are received by setting the android:priority attribute
for the relevant intent-filter tag in the manifest. Another feature of ordered broadcasts is that by using
abortBroadcast(), setResultCode() and setResultData(), a receiver can set a result that is
delivered back to the broadcaster or abort the broadcast so that the Intent won’t be propagated to the next
receiver in the queue.
Broadcaster
Receiver A
Receiver B
Receiver C
Receiver D
Figure 8-1 Diagram illustrating the asynchronous normal
broadcasts to multiple receivers
Broadcaster
Receiver A
Receiver B
Receiver C
Receiver D
Figure 8-2 Diagram illustrating the sequential propagation
of ordered broadcasts
The following code shows the implementation of a receiver that expects an ordered broadcast. You start by
checking that the broadcast is in fact ordered and then assign the result code, result data and any extras you
want to deliver to the broadcaster. After the onReceive() method returns, the response will be sent back to
the broadcaster automatically.
public class OrderedReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if(isOrderedBroadcast() {
setResultCode(Activity.RESULT_OK);
setResultData(“simple response string”);
// Get current response extras, or create new if null.
Bundle resultExtras = getResultExtras(true);
// Set our component name for the extras response...
resultExtras.putParcelable(“componentName”,
new ComponentName(context,getClass()));
}
}
}
In the following code, you see how to send an ordered broadcast and how responses are handled.
You can register for responses by passing a BroadcastReceiver object to the method Context.
sendBroadcast(). This receiver will then get a call to onReceive() for every receiver of the original
ordered broadcast.
public void sendOrderedBroadcastAndGetResponse() {
Intent intent = new Intent(ACTION_ORDERED_MESSAGE);
// The broadcast receiver that will handle responses
BroadcastReceiver responseReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String resultData = getResultData();
Bundle resultExtras = getResultExtras(false);
if (resultExtras != null) {
ComponentName registeredComponent = resultExtras.
getParcelable(“componentName”);
}
// TODO Handle response
}
};
sendOrderedBroadcast(intent, responseReceiver, null,
RESULT_OK,null,null);
}
You rarely need to send an ordered broadcast in your own applications, but they can be helpful if you’ll be
communicating with other applications (for instance, plug-ins can have a great use for this). In the Android
system, the most common example of ordered broadcasts is when an application listens for incoming SMS
messages, which is part of the hidden APIs. I describe this in further detail in Chapter 15.
Sticky Broadcasts
A variation of the normal broadcast is a sticky broadcast, which works a little bit differently. The difference is
that the Intent sent with Context.sendStickyBroadcast() will “stay around” after the broadcast is
complete, allowing future registrations for the matching Intent to receive the same broadcast as well.
One example of such a broadcast is Intent.ACTION_BATTERY_CHANGED, which is used to indicate
changes in the battery level for the device. Another example is Intent.ACTION_DOCK_EVENT, which
indicates whether a device is placed in a dock. Check the Android API documentation for other examples of
sticky broadcasts. The following code shows an example of a receiver that listens for battery changes. It will also
notice whether the sticky broadcast is new or not.
public class BatteryChangeListener extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(Intent.ACTION_BATTERY_CHANGED.equals(action)) {
if(isInitialStickyBroadcast()) {
// This is an old event from the “sticky” cache
} else {
// This is a new event that just occurred...
}
}
}
}
This method is especially useful for signaling system-wide states (such as battery status). In order for
an application to send this type of broadcast, it must hold the permission android.permission.
BROADCAST_STICKY and send sticky broadcasts using Context.sendStickyBroadcast().
Use sticky broadcasts with care from your own application, because it will put an additional load on
the available system resources.
Directed Broadcasts
Another variation of the normal broadcasts is directed broadcasts. These broadcasts use a feature of the
intent-filter with which you can explicitly specify the receiver by setting the ComponentName
in the broadcasted Intent. This is the combination of the class- and package-name of the registered
BroadcastReceiver, as shown in the following code :
public void sendDirectedBroadcast(String packageName, String className,
String action) {
Intent intent = new Intent(action);
intent.setComponent(new ComponentName(packageName, className));
sendBroadcast(directedBroadcastIntent);
}
The result of the previous code is that you send a normal broadcast that will be received only by the specified
receiver, even if other receivers are registered for the same Intent action. Note: You must know both the
package-name and the class-name of the receiver for this to work.
The directed broadcast approach can be very useful for applications that provide plug-in functionality. When
a plug-in is registered (installed), it can signal to the main application the relevant information for a directed
broadcast.
Enabling and Disabling Receivers
When the broadcast you want to listen for is available only for receivers declared in the manifest, you can
reduce the impact on the system’s load another way. With the PackageManager, you can enable and disable
components in your application, which is helpful if you have a receiver that you want to be inactive unless the
user performs a specific action (for instance, changes a setting).
The two methods that follow illustrate how you can programmatically enable and disable a specific component
based on its ComponentName. You just set android:enabled=”false” in the manifest as the default
value for that component and later use the following code to change the value to true.
public void enableBroadcastReceiver() {
PackageManager packageManager = getPackageManager();
packageManager.setComponentEnabledSetting(
new ComponentName(this, ChargerConnecedListener.class),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
}
public void disableBroadcastReceiver() {
PackageManager packageManager = getPackageManager();
packageManager.setComponentEnabledSetting(
new ComponentName(this, ChargerConnecedListener.class),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
Notice the use of PackageManager.DONT_KILL_APP as the last parameter to
setComponentEnabledSetting(). This will prevent the platform from killing the application, which is
otherwise the default behavior when changing this property.
You can use this method for enabling and disabling components for Activities as well (and
also Services and ContentProviders). It’s an efficient way to toggle the visibility of your
application’s icons in the launcher (also called, home-screen application tray). You could, for instance,
show only the icon for the setup Activity after installation and later hide it using this method after
the setup is complete. You can also use this method to show the icon for an Activity that shouldn’t
be visible until the user completes the setup.
System Broadcast Intents
The Android API defines many different broadcast actions that relate to different system events. In earlier code
examples in this chapter, I’ve shown a few of these, like changes in battery level or when the power to the
devices was connect and disconnected. Because these events are spread out in different places relating to their
respective function, finding a broadcast for a specific event can be difficult, even with the Android developer
site’s search function. Maybe you don’t even know that a certain event exists, or perhaps you get too many
irrelevant matches. Also, some very useful broadcast Intent actions aren’t publically specified in the API,
and finding them requires some understanding of the hidden Android APIs. (I go into the details of the hidden
Android APIs in Chapter 15.)
In this section, you will find some of the most commonly used system events, along with some examples of
when to use them. Several others are available, and the only way to find them is to know what you’re looking
for and search the official Android APIs. A good place to start when looking for their definitions is in the
Intent class.
Auto-Starting Your Application
One of the frequent questions Android application developers ask me is how they can make their applications
start automatically. The short answer is that you can’t, not directly. However, you can register for certain events
that eventually will be triggered and which you can use to start your application. There is also an event that is
sent to an application after it’s upgraded from an earlier version (usually after an update is downloaded and
installed from Google Play).
The following code shows the declaration of a receiver in the manifest that listens to the Intent.ACTION_
BOOT_COMPLETED and Intent.ACTION_MY_PACKAGE_REPLACED broadcasts. Note: The receiver
is disabled by default. This is a good practice, especially when listening for Intent.ACTION_BOOT_
COMPLETED; otherwise your application will start every time the device is booting up, possibly waste your
system’s resources.
<receiver android:name=”.StartupListener” android:enabled=”false”>
<intent-filter>
<action android:name=” android.intent.action.BOOT_COMPLETED” />
<action android:name=” android.intent.action.MY_PACKAGE_REPLACED”
/>
</intent-filter>
</receiver>
Enable receivers for these broadcasts only when doing so is necessary, such as after the user changes
a setting in your application or enables certain features—for example, an alarm clock application in
which the receiver is left disabled until the user schedules an alarm. Use the code from the earlier
example for enabling (and disabling) this receiver as needed.
User Presence and Screen State
When a user locks a device (that is, presses the power button to turn off the device), the current Activity
receives a call to onPause(), signifying that it has lost focus. Similarly, the Activity receives a call to
onResume() when it regains focus after the lock screen is disabled. Normally, applications don’t need
additional information, but what if you have a Service that needs to be notified every time the user unlocks
the device or when the screen goes on or off? Luckily, there are broadcasts for these events as well, as shown in
the following code block.
<receiver android:name=”.UserPresentListener”>
<intent-filter>
<action android:name=” android.intent.action.SCREEN_OFF” />
<action android:name=” android.intent.action.SCREEN_ON” />
<action android:name=” android.intent.action.USER_PRESENT” />
</intent-filter>
</receiver>
Intent.ACTION_SCREEN_ON and Intent.ACTION_SCREEN_OFF are sent as the device screen goes on
and off, respectively. Intent.ACTION_USER_PRESENT is sent when the user deactivates the lock screen. A
simple diagram showing how these actions are broadcast is shown in Figure 8-3.
Send Intent.ACTION_USER_PRESENT
Screen on
Screen off
Power button
Send Intent.ACTION_SCREEN_OFF
and device
and device
pressed
unlocked
locked
Power button pressed
Screen on
Send Intent.ACTION_SCREEN_ON
User unlocks
and device
device
locked
Figure 8-3 A simple diagram illustrating when broadcasts are sent for the events when
the screen goes on and off and when the user disables the lock screen
Network and Connectivity Changes
For many Android applications, one of the most important things to keep track of is the state of the network
and what kind of connectivity the device currently has. It’s good practice to limit your application’s use of the
network according to the available bandwidth.
Most Android devices have two types of networks, cellular and Wi-Fi. If your application operates heavily on
a network, you may want to defer transfers until the device is connected to a Wi-Fi; otherwise, you may incur
considerable cost if you transfer data for users of mobile networks such as 3G or LTE. These events can also be
useful for detecting when a user connects to a well-known Wi-Fi, like a corporate intranet, where it’s safe to
transfer sensitive data.
Connectivity and network broadcast actions are handled by different parts of the Android API. The
action ConnectivityManager.CONNECTIVITY_ACTION is broadcast whenever a general change
in network connectivity occurs, such as switching from Wi-Fi to mobile data. When this is received, the
ConnectivityManager service can be retrieved using Context.getService() which lets you gain
more detailed information about the current network.
However, to get more fine-grained information about the current network, you also need to listen for broadcast
actions from TelephonyManager (for cellular mobile data network events) and WifiManager (for Wi-Fi–
related events). TelephonyManager lets you query the type of mobile data connection, and WifiManager
gives you access to retrieval of the state of the Wi-Fi connection and the different IDs related to a Wi-Fi (SSID
and BSSID).
The following code is a simplified example of how to detect when a device enters a preconfigured “home” Wi-Fi,
which is effective for applications configured to talk to servers or for media centers that are available only on a
specific Wi-Fi.
public class CheckForHomeWifi extends BroadcastReceiver {
public static final String PREFS_HOME_WIFI_SSID = “homeSSID”;
public void onReceive(Context context, Intent intent) {
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(context);
String homeWifi = preferences.getString(PREFS_HOME_WIFI_SSID, null);
if(homeWifi != null) { // Only check if home WiFi is set
NetworkInfo networkInfo =
intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if(networkInfo != null &&
networkInfo.getState().equals(NetworkInfo.State.CONNECTED))
{
WifiInfo wifiInfo =
intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
if(wifiInfo != null
&& homeWifi.equals(wifiInfo.getSSID())) {
// Success - We’re on out home WiFi!
} else {
// Fail - We’re on some other WiFi!
}
}
}
}
}
In this following example, you listen for changes from the ConnectivityManager and determine whether
you’re on a mobile data network. If you receive mobile data, you make an additional check using the
TelephonyManager to see if you’re on a 3G or LTE network.
public class WhenOn3GorLTE extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ConnectivityManager.CONNECTIVITY_ACTION) {
boolean noConnectivity = intent.
getBooleanExtra(ConnectivityManager.
EXTRA_NO_CONNECTIVITY, false);
if(noConnectivity) {
// No network at all.. :(
} else {
int networkType = intent.
getIntExtra(ConnectivityManager.
EXTRA_NETWORK_TYPE,
ConnectivityManager.TYPE_DUMMY);
if(networkType == ConnectivityManager.TYPE_MOBILE) {
checkfor3GorLte(context);
}
}
}
}
private void checkfor3GorLte(Context context) {
TelephonyManager telephonyManager = (TelephonyManager) context.
getSystemService(Context.TELEPHONY_SERVICE);
switch (telephonyManager.getNetworkType()) {
case TelephonyManager.NETWORK_TYPE_HSDPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
case TelephonyManager.NETWORK_TYPE_HSPAP:
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_LTE:
// Yay - we got fast enough mobile data! :)
break;
default:
// Slow mobile network - notify user...
break;
}
}
}
Device Configuration Changes
Whenever you turn an Android device from portrait to landscape orientation, an event is triggered by the
system causing a configuration change. Several related triggers all cause this event, such as changing the UI
mode on the device or when keyboard visibility changes.
Configuration changes are a little tricky to manage on Android. For an Activity, the default behavior
is to restart, which means that onPause(), onStop(), and onDestroy() are called and your
Activity instance is lost, including all its data. To avoid having the Activity restart when a certain
configuration change occurs, you can declare these events in the android:configChanges attribute of
the Activity tag. As a result, the method Activity.onConfigurationChanged() is called with the
new Configuration object, instead of your Activity restarting. Use this method only as a last resort—
for instance, if you have a full-screen game or application with custom handling of changes in the device’s
orientation.
The standard way of handling configuration changes for an Activity is through the default 'margin-top:12.0pt;margin-right:0cm;margin-bottom: 12.0pt;margin-left:0cm;line-height:normal'>system restart it. This works well for most situations, but in some cases, you may want to avoid restarting your
Activity because restarting it could have a negative impact on performance and user experience.
For the Service, ContentProvider, and Application components, you can also use the
onConfigurationChanged() method. The difference is the Activity class is called for all configuration
changes without you having to add attributes in the manifest. Also, for these components, configuration
changes will not force them to restart (which is good because otherwise Services would restart when you
rotate your device). This allows your Service, or some other background component, to detect when the user
rotates the device or when another application changes the UI mode.
Summary
In this chapter, I introduced advanced concepts of the BroadcastReceivers that Android developers might
not be familiar with. These receivers are powerful tools when it comes to reacting to system-wide events and
also for communicating within your application, using the LocalBroadcastManager, or between multiple
applications.
I explained the difference between normal, ordered, sticky, and directed broadcasts and when and how to
apply them. Next, I showed how to enable and disable receivers, and other components, in your code to ensure
that they’re activated only when necessary. This powerful feature in Android should be used more often in
applications to reduce their load on the overall system.
Many different broadcast Intent actions are defined by the Android platform. I discussed a few of the most
useful broadcasts and gave a few examples of when and how to use them. Most of them are documented in the
official Android APIs, but some are part of the hidden APIs that I cover in more detail in Chapter 15.
Finally, I covered how to detect configuration changes of the device, including screen orientation, keyboard
state, and UI mode. By default, your Activity is restarted when a configuration change occurs, but you can
override this using the android:configChanges attribute in the manifest. You can also listen for these
changes by overriding the method onConfigurationChanged() in your Service components, which
can help with receiving notifications about state changes in devices that lack a system broadcast.
BroadcastReceivers and configuration changes are often overlooked in Android applications, but as a
skilled developer, you can use them to create a smooth user experience and to react to changes that could
otherwise cause unexpected behavior in an application.
Further Resources Documentation
For handling runtime configuration changes, go to http://developer.android.com/guide/topics/resources/runtime-changes.html