How to Broadcast in Android

Dec 29, 2021
hackajob Staff

You asked, and we listened. We have a brand new Android tutorial looking at how to broadcast in this operating system. What is broadcasting, how does it work and, more importantly, how can you do it? We get into it all here, check it out below!

What are Broadcast Receivers?

Broadcast receivers handle events when they occur and generate a message for the apps registered. An application must send or receive data inside or outside its scope. So for this specific purpose, a Broadcast Receiver comes in very handy. It's an android component that allows users to handle system or application events in a way similar to the publish-subscribe design pattern. Don't know what we mean? Let's find an example.

For example when the device boots up or starts charging, it will immediately notify other apps that are interested (registered) to receive a broadcast for change of state. Your Ebook reader gets notified by getting a broadcast message when a new book gets downloaded. A new photo taken or downloaded will generate a broadcast for the phone gallery and related apps. A similar pattern is followed by all the apps for the events of their interest.

How do System Broadcasts Work?

The system automatically sends various broadcasts when an event like airplane mode is enabled or disabled, a new wifi connection becomes available, the battery runs out, time zone and settings change to a new region, if location is enabled or disabled, etc. These types of broadcasts are sent to all the apps subscribed to receive the event. Broadcast messages are wrapped in an Intent object, that identifies the action in the event. An android.intent.action.AIRPLANE_MODE string is an example of a broadcast wrapped inside the Intent. This string may contain an extra Boolean variable that indicates the state of the action, i.e. airplane mode ON or OFF.

What are the Effects on the Process State?

The state of the Broadcast Receiver is always saved in its containing process, no matter what state it has, or whether it is  running or not. Suppose that your receiver is hosted in onResume(). This wold mean it's in an execution state. It will be considered as a foreground process and the system will keep running it except if there's excess memory usage. When the code returns from onResume(), the state of your broadcast is saved and the system kills it. This will free up the resources for other processes.

It's also highly recommended to not allow your receiver to run in the background as it will unnecessarily take up resources. If you do so, when the system processes the returns from the onReceive(), it might kill the process any time. It can also kill any child process that you want to keep threading in the background. So as you can see, you probably shouldn't  start long-running background threads from a broadcast receiver. To avoid this, you should either call a going() or schedule a JobService from the receiver using JobScheduler.

The following code snippet shows a BroadcastReceiver() that uses a goAsync() to indicate that it needs more time to finish after onReceive is returned. This is very useful to free up the resource and keep the task in check while running in the background.

private const val TAG = "MyBroadcastReceiver"

class MyBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        val pendingResult: PendingResult = goAsync()
        val asyncTask = Task(pendingResult, intent)
        asyncTask.execute()
    }

    private class Task(
            private val pendingResult: PendingResult,
            private val intent: Intent
    ) : AsyncTask<String, Int, String>() {

        override fun doInBackground(vararg params: String?): String {
            val sb = StringBuilder()
            sb.append("Action: ${intent.action}\n")
            sb.append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
            return toString().also { log ->
                Log.d(TAG, log)
            }
        }

        override fun onPostExecute(result: String?) {
            super.onPostExecute(result)
            // Must call finish() so the BroadcastReceiver can be recycled.
            pendingResult.finish()
        }
    }
}

Local Broadcast

The latest Android framework has built-in support for Broadcast Receiver so you don't have to add the dependencies in your project or app module. However, it's not the case for older projects as you need to ask for dependency in the in-app module’s build.gradle file.

compile ‘com.android.support:support-v4:23.4.0’

To use LocalBroadcastManager class, you have to create the following instance:

LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);

Now sendBroadcast() can send local broadcast using:

// Create intent with action
Intent localIntent = new Intent(“CUSTOM_ACTION”);
// Send local broadcast
localBroadcastManager.sendBroadcast(localIntent);

Now create a broadcast receiver that can respond to the local-broadcast action:

private BroadcastReceiver listener = new BroadcastReceiver() {
@Override
    public void onReceive( Context context, Intent intent ) {
        String data = intent.getStringExtra(“DATA”);
        Log.d( “Received data : “, data);
    }
};

The broadcast receivers you registered earlier must be unregistered dynamically when they are no longer necessary like:

localBroadcastManager.unregisterReceiver(myBroadcastReceiver);

Receiving Broadcast

An app can register for receiving a specific broadcast. When a broadcast is generated, the system automatically routes the .message to the apps registered to this specific event. An app can subscribe to receive a broadcast in two ways that are discussed in detail below:

a. Manifest-Declared

An app becomes the receiver by declaring the registration in its AndroidManifest.xml file. Broadcast receiver launches the app if already not running when the event occurs. Here is the code for using manifest-based broadcast:

You have to specify the <receiver> element in your app's manifest first.

<receiver android:name=".MyBroadcastReceiver"  android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
    </intent-filter>
</receiver>

These Intent filters specify the broadcast actions your app receiver subscribed to.

Secondly, create a subclass BroadcastReceiver and implement the onReceive(Context, Intent) method. The broadcast receiver in the following example logs and displays the contents of the broadcast:

private const val TAG = "MyBroadcastReceiver"

class MyBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        StringBuilder().apply {
            append("Action: ${intent.action}\n")
            append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
            toString().also { log ->
                Log.d(TAG, log)
                Toast.makeText(context, log, Toast.LENGTH_LONG).show()
            }
        }
    }
}

The system package manager registers the receiver on app installation. This receiver becomes a stand-alone entry point, which means that the system can start the app and deliver the broadcast even if the app is not running.While running the app, the system creates a new BroadcastReceiver component to handle this object. This is only valid for the duration of the function call to onReceive(Context, Intent). Once the function is returned, the object becomes invalid and no longer exists.

b. Context-Registered

An app becomes the receiver by dynamically calling the Context.register receiver() method. To register a receiver with context, you have to perform the following operations.

val br: BroadcastReceiver = MyBroadcastReceiver()

Now Create an IntentFilter and register the receiver by calling registerReceiver(BroadcastReceiver, IntentFilter):

val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
    addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
}
registerReceiver(br, filter)

Context-registered receivers receive the broadcast as long as their context is valid. For example, if you registered with an Activity context, you'll receive a broadcast as long as the Activity is not destroyed. For an Application context registered receiver, it will be valid as long as your activity is running. You cannot just leave the broadcast registered with the context, you have to stop receiving broadcasts by calling unregisterReceiver(android.content.BroadcastReceiver). Be sure to unregister the receiver when you no longer need it or the context is no longer valid.

Sending Broadcasts

In Android, you can send a broadcast in three different ways:

sendOrderedBroadcast

You can send a broadcast to one receiver at a time using the sendOrderedBroadcast(Intent, String) method. During execution, either each receiver can propagate a result to the next receiver, or it can abort the broadcast completely. An android:priority attribute can be used to sort out the order of receivers to run. Receivers having the same priority will be run in an arbitrary order.

sendBroadcast

To send a Broadcast to all receivers at a time, you can use the sendBroadcast(Intent). It executes in an undefined order. It's also known as a Normal Broadcast. This is a more efficient way to broadcast. Receivers in this method can neither read results from to and from each other, nor propagate data received, nor abort the broadcast when require.

LocalBroadcastManager.sendBroadcast

The LocalBroadcastManager.sendBroadcast is used to send broadcasts to receivers that are present in the same app as the sender. If you don't need to send broadcasts across apps, use local broadcasts. It's an efficient way and you don't have to worry about security loopholes. No other app can send or receive your broadcast.

Problems with Broadcast

Broadcasting does have it's issues when declared globally. You don't have to declare a global broadcast as it shares the message with every app registered for receiving. This can be problematic when the data is sensitive. To create a secure and less thread-based efficient structure, an app must use Local Broadcast.

Local Broadcasts stop the user from broadcasting globally. Similarly, other apps are also unable to send messages to your app and avoid loopholes. Using LocalBroadcastManager class, your app creates efficient threads and reduces the overhead of global broadcast.

You have to be careful while registering and unregistering the receiver, for example, if you register a receiver in onCreate(Bundle) using the activity context, then you should unregister it in onDestroy(). It will prevent the receiver from leaking out of the activity. Similarly, if you register the receiver in onResume(), you should unregister it in onPause(), otherwise, it will register the activity multiple times. If you don't want to receive a broadcast when paused, you can cut down unnecessary system overhead. Never unregister in onSaveInstanceState(Bundle), because this isn't called if the user moves back in the history stack.

Conclusion

So that's it! In this tutorial, we learned about the Broadcast Receivers and Sender and how it's a generated message within or outside the app whenever an event of interest occurs. A broadcast can be sent or received by the system or an app that has permission to perform a task. The system notifies all the apps registered for a particular broadcast to receive and it can kill any process after its host process is returned. Whew! We hope this was helpful.

And that's it! Like what you've read or want more like this? Let us know! Email us here or DM us: Twitter, LinkedIn, Facebook, we'd love to hear from you.