A BroadcastReceiver is an Android app component that responds to system-wide broadcast announcements. Imagine an event like external power being connected/disconnected from the device, screen turning on/off, battery getting low or picture captured. All these events originate from the system. Infact apps themselves can also initiate broadcasts – for example the SMS app broadcasting that an SMS has being received and let other apps know about this event so that they can trigger some action. Unlike Activities broadcast receivers do not have any user interface but may create a status bar notification. It is intended to do minimal amount of work and can delegate hardcore jobs to Services. It receives an Intent object, so if you’ve read my previous articles on Intents and Intent Filters you’ll have an easy time learning it.
So the gist is broadcast receivers are like dormant app components that can register for various system or application events (intents). Once any of those events occur the system notifies all the registered broadcast receivers and brings them up into action which could be notifying the user or perform some other job. The registration is done in the manifest file using intent filters (static) but can also be done programatically (dynamic).
What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.
Creating a BroadcastReceiver
Let’s quickly see how to implement a broadcast receiver.
public class MyReceiver extends BroadcastReceiver { public MyReceiver() { } @Override public void onReceive(Context context, Intent intent) { // This method is called when this BroadcastReceiver receives an Intent broadcast. Toast.makeText(context, "Action: " + intent.getAction(), Toast.LENGTH_SHORT).show(); } }
We call our broadcast receiver implementation MyReciever
and it must be a direct or indirect subclass of BroadcastReceiver
which is an abstract class with the onReceiver()
method being abstract. Whenever an event occurs Android calls the onReceive()
method on all registered broadcast receivers. If you notice the Intent
object is passed with all the additional information required and also you’ve the Context
object available to do other tasks like maybe start a service (context.startService(new Intent(this, TestService.class));
).
Registering the Broadcast Receiver
We’re done with the creation but it needs to be registered so that it can receive events (intents). There are two ways to do this:
- Statically in the manifest file.
- Dynamically in the code.
Registering BroadcastReceiver in the Manifest File
This is super easy, let’s see how.
<receiver android:name="com.pycitup.pyc.MyReceiver" android:enabled="true" android:exported="true" > <intent-filter> <action android:name="com.pycitup.BroadcastReceiver" /> </intent-filter> </receiver>
We use the <receiver>
tag to register our broadcast receiver with an intent filter. If you’re not familiar with intent filters then do read up my previous article. Basically using intent filters we tell the system any intent that matches our criterias (subelements) should get delivered to that specific app component (a broadcast receiver in this case).
Registering BroadcastReceiver Programatically
Alternatively the registration can be done in the code itself, rather than the manifest. Let’s see how:
IntentFilter filter = new IntentFilter("com.pycitup.BroadcastReceiver"); MyReceiver myReceiver = new MyReceiver(); registerReceiver(myReceiver, filter);
First we created an IntentFilter
object that specifies which event/intent our receiver will listen to. In this case it’s com.pycitup.BroadcastReceiver
which is a custom action name, could be anything but generally the java package naming convention is followed. We’ll use this action name again while sending a broadcast that will be handled by this receiver.
Then we instantiate our broadcast receiver and call Context.registerReceiver()
to actually register our receiver that will be called and run in the main application thread.
It’s important to note that when we register a receiver in this way, it lives for as long as the component that does the registration lives. Once the component that had made the registerReceiver()
call is destroyed sendBroadcast()
will also stop working, hence the receiver won’t receive anymore be it an event generated from an app or the system. Whereas with the previous method where we registered via the manifest file, this is not the case. When registering a receiver in Activity.onResume()
, it is strongly suggested to unregister them receivers in Activity.onPause()
to avoid unnecessary system overhead as intents won’t be received when paused anyway.
@Override protected void onPause() { unregisterReceiver(mReceiver); super.onPause(); }
Which Method to Use When for Registration
Which method (static or dynamic) to use when depends completely upon what you’re trying to do. Basically when you want to do some changes right on the screen (home screen, launcher, status bar, etc.) by showing up some notification or some indicator in the status bar by listening to system wide events or maybe those sent by other apps, then it make sense to use statically registered broadcast receivers. Whereas based on similar events you want to do changes right in your app when the user is using it or maybe it’s put in the background, then it makes sense to use dynamically registered receivers which’ll last till the registering components are destroyed.
Infact there are certain events like Intent.ACTION_TIME_TICK
that cannot be registered in the manifest but only via registerReceiver()
to prevent battery drainage.
Creating and Sending the Broadcast Event/Intent
We’ve seen how to create a broadcast receiver and then register it statically or dynamically. Finally we need to learn how to create a broadcast intent and send it to our receiver.
Intent intent = new Intent(); intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); intent.setAction("com.pycitup.BroadcastReceiver"); intent.putExtra("Foo", "Bar"); sendBroadcast(intent);
We created an Intent
, set the action to the one declared in our manifest file too, put sample data (Foo="Bar"
) in it and send it using sendBroadcast()
. This Context
method broadcasts the intent to all the interested broadcast receivers. The receivers are called asynchronously, i.e., sendBroadcast()
returns immediately and the execution continues while the receivers are run but doesn’t wait until their completion. No results are propagated from the receivers.
If you notice the second line where I set FLAG_INCLUDE_STOPPED_PACKAGES
– that’s interesting. This flag basically means include intent filters of stopped applications too in the list of potential targets to resolve against. Similarly there is FLAG_EXCLUDE_STOPPED_PACKAGES
that does the opposite, i.e., excluding. When neither or both of them are specified then the default behaviour is including but in case of broadcast receivers the system adds FLAG_EXCLUDE_STOPPED_PACKAGES
to all broadcast intents by default. More on this subject here.
STOPPED STATE of an app is when it is installed but not launched or force stopped from the application manager tool. More on this here.
Explicit Broadcast Intents
If you notice we actually created implicit intents so that multiple registered receivers could receive them when dispatched using sendBroadcast()
. We can also create an explicit broadcast intent where we explicitly specify the receiver class. Here’s an example:
Intent intent = new Intent(this, MyReceiver.class); sendBroadcast(intent);
Don’t forget to statically register your receiver in the manifest:
<receiver android:name="com.pycitup.pyc.MyReceiver" />
Normal and Ordered Broadcasts
Broadly there are two major classes of broadcasts:
- Normal Broadcasts: These are sent with
Context.sendBroadcast()
as you just saw sometime back. They’re completely asynchronous, i.e., the broadcasts events/intents are received by all the receivers in an asynchronous fashion. The receivers are run in an undefined order, often at the same time. It’s efficient but receivers cannot use results from other receivers or abort the entire chain of execution at a certain level. - Ordered Broadcasts: These are sent with
Context.sendOrderedBroadcast()
. They’re delivered to one receiver at a time. The order can be controlled withandroid:priority
attribute of the matchingintent-filter
. Receivers with same priority will be executed in a random order. As each receiver executes, it can trasmit the result to the next one or even abort the entire broadcast chain so that no other receivers receive the broadcast intent and are executed.
We discussed normal broadcasts previously, hence in this section we’ll go through ordered broadcasts. Let’s straight away get into some code. First we’ll define two broadcast receivers:
// MySecondReceiver.java public class MySecondReceiver extends BroadcastReceiver { private String TAG = MySecondReceiver.class.getSimpleName(); public MySecondReceiver() { } @Override public void onReceive(Context context, Intent intent) { Bundle results = getResultExtras(true); results.putString("hierarchy", TAG); Log.d(TAG, "MySecondReceiver"); } }
// MyReceiver.java public class MyReceiver extends BroadcastReceiver { private String TAG = MyReceiver.class.getSimpleName(); public MyReceiver() { } @Override public void onReceive(Context context, Intent intent) { Bundle results = getResultExtras(true); String hierarchy = results.getString("hierarchy"); results.putString("hierarchy", hierarchy + "->" + TAG); Log.d(TAG, "MyReceiver"); } }
Pretty simple to understand, just put this also in your manifest to register the receivers:
<receiver android:name="com.pycitup.pyc.MyReceiver" > <intent-filter android:priority="1"> <action android:name="com.pycitup.BroadcastReceiver" /> </intent-filter> </receiver> <receiver android:name="com.pycitup.pyc.MySecondReceiver" > <intent-filter android:priority="2"> <action android:name="com.pycitup.BroadcastReceiver" /> </intent-filter> </receiver>
Note how we set the priority using android:priority
. The second one with higher priority will get to receive the broadcast intent first and then the first one with a priority of 1
will get to receive. By default, the priority is 0
on intent filters and must be between IntentFilter.SYSTEM_LOW_PRIORITY
and IntentFilter.SYSTEM_HIGH_PRIORITY
.
Finally here’s the snippet I’ll put in my MainActivity.onCreate()
:
// In MainActivity.onCreate() IntentFilter filter = new IntentFilter("com.pycitup.BroadcastReceiver"); // filter.setPriority(10); // could do this if you want to registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Bundle results = getResultExtras(true); String hierarchy = results.getString("hierarchy"); results.putString("hierarchy", hierarchy + "->" + TAG); Log.d(TAG, "Anonymous class broadcast receiver"); } }, filter); Intent intent = new Intent("com.pycitup.BroadcastReceiver"); sendOrderedBroadcast(intent, null, new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Bundle results = getResultExtras(true); String hierarchy = results.getString("hierarchy"); System.out.println(hierarchy); Log.d(TAG, "Final Receiver"); } }, null, Activity.RESULT_OK, null, null);
Create an intent filter first and dynamically registered a new receiver with it. Then finally we created the broadcast intent and fired it via sendOrderedBroadcast()
. The third receiver argument that you see is the result receiver, so once all the receivers are executed in order, it’ll get to receive the event finally.
In each and every onReceive()
I kept on getting the result extra data as set by the previous receiver using getResultExtras()
and then set a new string representing the trail of reception or the hierarchy. Finally the dump should seem like this:
﹕ MySecondReceiver ﹕ MyReceiver ﹕ Anonymous class broadcast receiver ﹕ MySecondReceiver->MyReceiver->MainActivity ﹕ Final Receiver
You can always abort the broadcast by calling abortBroadcast()
in any receiver’s onReceive()
preventing further receivers from receiving the event. Although the final receiver’s onReceive()
, set in the sendOrderedBroadcast()
will still be called.
LocalBroadcastManager
The LocalBroadcastManager
class is a helper class to register local broadcast receivers as well as send broadcasts of Intents to them within our own app’s process rather than sending global broadcasts using sendBroadcast()
as we saw before. It is more efficient as well as secured compared to global broadcasts through the system as the data won’t leave our app nor can we receive from other foreign apps. Let’s see a really simple example of how to use it:
// Generally in your onResume() LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String message = intent.getStringExtra("foo"); Log.d("LocalBroadcastManager", "foo : " + message); } }, new IntentFilter("my-custom-event"));
We saw how to register, now here’s how to send an intent to it:
// Send Intent intent = new Intent("my-custom-event"); intent.putExtra("foo", "bar"); LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
Based on the action set which is my-custom-event
, it’ll send the Intent to our registered receiver to which we had passed an IntentFilter
with the same action name.
We shouldn’t forget to unregister the receiver actually. So if we’re saving the receiver in an instance member called mReceiver
then here’s how we should unregister it in onPause()
:
@Override protected void onPause() { LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); super.onPause(); }
Asynchronous Processing
Generally after the execution of onReceive()
of the receiver class is finished, the Android system is allowed to recycle the receiver, i.e., another intent can be passed to it. For potentially long running operations it is recommended to trigger a service instead on the context
object passed to it. This is a reason why any asynchronous operation wasn’t allowed to perform till API level 11. But that has changed now.
Since API 11, if you want to pass on the processing of some task in another thread you could do something like this:
// Sample code from: http://stackoverflow.com/a/22741384 final PendingResult result = goAsync(); Thread thread = new Thread() { public void run() { int i; // Do processing result.setResultCode(i); result.finish(); } }; thread.start();
Using goAsync()
we returned an object of the type PendingResult
on which calling the finish()
method indicates the Android system that the receiver is no more alive and can be recycled.
Pending Intents
PendingIntent is sort of an intent whose execution can be delayed and not executed right away using something like startActivity()
or startActivityForResult()
, but in the future. It’s an object which acts as a wrapper around an Intent object and passed on to another app. This way we can grant permission to the foreign application to execute the underlying Intent as if it were executed from our very own app’s process.
When an Intent
is given to a foreign app that ships with Android or is a third party app, then they execute it with their own permissions. Whereas a PendingIntent
can wrap that Intent
which the foreign app executes with your own app’s permission.
If you need a more elaborated explanation of it, there’s a nice SO answer on it that you could read although I pretty much covered the gist.
Let’s see an example where we’ll create a PendingIntent and pass it on to an AlarmManager using which we’ll access the system’s alarm services that’ll allow us to schedule a piece of code to be run at some point in the future (in our case after 3 seconds). The example should give us some clarity on pending intents:
// In the MainActivity.onCreate() int seconds = 3; // Create an intent that will be wrapped in PendingIntent Intent intent = new Intent(this, MyReceiver.class); // Create the pending intent and wrap our intent PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0); // Get the alarm manager service and schedule it to go off after 3s AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (seconds * 1000), pendingIntent); Toast.makeText(this, "Alarm set in " + seconds + " seconds", Toast.LENGTH_LONG).show();
So we create an explicit intent for our broadcast receiver MyReciever
and wrap that inside pendingIntent
. Then we get the alarm manager and schedule one that’ll go off in 3s eventually executing the pending intent. Now let’s write a short piece of code in our MyReceiver.onReceive()
that’ll vibrate when the pending intent is execute 3s later.
To understand AlarmManager.RTC_WAKEUP
passed to AlarmManager.set()
, you may read this SO answer.
In our broadcast receiver onReceive()
:
@Override public void onReceive(Context context, Intent intent) { // Vibrate for 2 seconds Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); vibrator.vibrate(2000); }
Now you may test this piece of code in your device (emulator won’t vibrate) and it’ll vibrate in 3s for 2s.
Using the PackageManager to Manipulate (Enable/Disable) Static Receivers
For some reason you might want your statically registered broadcast receiver to receive (execute) only once. If that’s the case then you can always disable them (or enable again when required) using the PackageManager
saving your battery from draining.
PackageManager pm = getPackageManager(); ComponentName compName = new ComponentName(getApplicationContext(), YourReceiver.class); pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
Supported states are:
COMPONENT_ENABLED_STATE_DEFAULT
– As specified in the manifest file.COMPONENT_ENABLED_STATE_DISABLED
– Sets the state to disabled.COMPONENT_ENABLED_STATE_ENABLED
– Sets the state to enabled.
The flag DONT_KILL_APP
prevents from killing the app that could happen since a component’s state change can make the app’s behaviour unpredictable.
Permissions
As discussed in my previous article too, some events require appropriate permissions. For example Intent.ACTION_BOOT_COMPLETED
requires the RECEIVE_BOOT_COMPLETED
permission. To specify this, you’ll have to put the following line in your manifest file:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
Always check the API reference to get the information on which permission to use for your events. Although most of them don’t require any extra permissions from what you might be using by default like the INTERNET
one.
Avoid Lengthy Jobs
We shouldn’t be executing jobs that lasts for long in broadcast receivers. Such jobs are usually meant to be executed by Services and hence can be delegated to them. They should only be doing minor tasks as they’re called on the UI thread which means a sluggish experience in case of a long running blocking operation.
Security
BroadcastReceivers are cross-application facility by nature. When you register receivers, other apps can send events to them and at the same time when you send broadcasts, others can receive them. To implement security and control permissions there are certain ways. The entire broadcast receivers security topic is elaborated in the documentation that you must read.
This SO thread has some good information on how to set custom permissions with a combination of uses-permission
tag, permission
tag, sendBroadcast()
with the permission string and also the android:permission
attribute on the receiver
tag. Here’s another thread with a really informative answer.
So the general idea is (based on my testings) that let’s say there are 2 apps where one is the receiver and the other is the sender.
- Now one of them will need to define a custom permission using the
<permission>
tag. - Also you’ll define the
android:permission
attribute on the<receiver>
tag (in the receiver app obviously). - Now to send the broadcast you’ll need
<uses-permission>
in the sender app. - Based on the presence of the same tag in the receiver app, your version of
sendBroadcast()
used in the sender app will change. If it is present then the two-parametersendBroadcast(intent, permission_string)
will have to be used where the second parameter is the permission string. If absent then the two-parameter will not work but have to use the single parameter version where you just send the intent.
The custom permission could be defined in either of them but make sure the one defining it is installed first else the other one will have to be re-installed or updated.
Summary
So we covered broadcast receivers in this article which are basically components triggered when an event occurs at the system level or app level based on which the app can change it’s own state or other’s like the status bar for instance.