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.
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
.
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.
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:
onReceive()
will freeze your UI, if you happen to have
a foreground activityonReceive()
, Android will terminate your
BroadcastReceiver
without waiting for onReceive()
to completeThis 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.
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 BroadcastReceiver
s will
not receive any broadcasts.
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.
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:
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.
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.
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>
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!");
}
}
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.
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>
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;
}
}
};
}
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:
EXTRA_HEALTH
, which should generally be BATTERY_HEALTH_GOOD
EXTRA_LEVEL
, which is the proportion of battery life remaining as an
integer, specified on the scale described by the EXTRA_SCALE
valueEXTRA_PLUGGED
, which will indicate if the device is plugged into AC
power (BATTERY_PLUGGED_AC
) or USB power (BATTERY_PLUGGED_USB
)EXTRA_SCALE
, which indicates the maximum possible value of level
(e.g., 100, indicating that level is a percentage of charge remaining)EXTRA_STATUS
, which will tell you if the battery is charging
(BATTERY_STATUS_CHARGING
), full (BATTERY_STATUS_FULL
), or discharging
(BATTERY_STATUS_DISCHARGING
)EXTRA_TECHNOLOGY
, which indicates what sort of battery is installed
(e.g., "Li-Ion"
)EXTRA_TEMPERATURE
, which tells you how warm the battery is, in
tenths of a degree Celsius (e.g., 213 is 21.3 degrees Celsius)EXTRA_VOLTAGE
, indicating the current voltage being delivered by the
battery, in millivoltsIn the case of BatteryFragment
, when we receive an ACTION_BATTERY_CHANGED
Intent
, we do three things:
ProgressBar
and TextView
to display the battery life as
a percentageBATTERY_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 inIf you plug this into a device, it will show you the device’s charge level:
Figure 320: The Battery Monitor
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 Intent
s”. 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()
.
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).
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.
Another variation on the broadcast Intent
is the ordered broadcast.
Normally, if you broadcast an Intent
, and there are 10 registered BroadcastReceiver
s
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:
BroadcastReceiver
at a time will receive the broadcastBroadcastReceiver
s receive the broadcast is (somewhat)
controlled by their developersBroadcastReceiver
can “abort” the broadcast, preventing other receivers in
the chain from receiving itSending 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.
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.
We examine LocalBroadcastManager
in more detail, along
with other event bus alternatives, later in the book.