Mastering BroadcastReceivers and Configuration Changes - Getting the Most Out of Components - Android Programming: Pushing the Limits (2014)

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