Thursday, 6 March 2014

Understanding BROADCAST RECEIVERS in Android : PART 2

We have discussed about broadcasts by Android system in the previous post PART1 and we are going to discuss about Broadcasts by the user in this post.

User also can send broadcasts whenever he wants. For ex: On click of a button OR on successful download of a file. So the receivers who are intended to receive this announcement can receive the message and react. Registering, Implementing and unregistering the receiver for broadcasts by the user are same as we discussed previously in PART1.  The only difference is user has to send the announcement of event explicitly. sendBroadcast() method is used to send the broadcasts explicitly.
We should use same action name while registering the receiver and sending the broadcast as below.

Registering statically:
<receiver android:name=".MyReciever" >
    <intent-filter>
      <action android:name="com.smartmobsolutions.custom_broadcast_action"/>
    </intent-filter>
</receiver>
Or you can register dynamically as below:
IntentFilter iff = new IntentFilter("com.smartmobsolutions.custom_broadcast_action");
registerReceiver(yourReceiverClassObject, iff);

On click of a button, you can send broadcasts in 2 simple steps:
1.      Create an intent by passing the action name to the constructor of Intent class. Or you can also create an empty intent and set the action by using setAction() of Intent class.  Optionally you can send some data to the receivers using putExtra() methods. You can get back this intent (which is being used to broadcast) as one of parameters of onReceive() method of your receiver class.
2.      Pass intent to sendBroadcast() method.

Intent intent = new Intent();
intent.setAction("com.smartmobsolutions.custom_broadcast_action");
intent.putExtra(“key”, your_data);
sendBroadcast(intent);

So, now we’ve sent a broadcast explicitly. All the receivers (means applications) who are registered for the same event will get triggered now.
public class MyReciever extends BroadcastReceiver{

            @Override
            public void onReceive(Context context, Intent intent) {
                        String data = intent.getStringExtra("key");
                              //The task to do when the required event happens.

            }
}
In this way also you can share the data across the applications.  You can set the priority if you want the receiver to get triggered first among all others as discussed in PART1.  
NOTE 1: As of Android 3.1 the Android system excludes all receivers from receiving intents by default if the corresponding application has never been started by the user or if the user explicitly stopped the application via the Android menu (in Manage → Application). This is an additional security feature as the user can be sure that only the applications he started will receive broadcast intents. This does not mean the user has to start the application again after a reboot. The Android system remembers that the user already started it. Only one start is required without a forced stop by the user.
NOTE 2: All the receivers are triggered in asynchronous manner. In other words, sendBroadcast() will send the broadcast and return immediately i.e. It doesn’t wait until all the receivers who are registered for matching event have triggered and executed.
(NOTE1 Attribution: Vogella)

Ordered Broadcasts: There might be a situation where you need to receive the result from the receiver to the calling component (i.e. most probably an activity). A normal broadcast will be received by receivers asynchronously and reacts. But an OrderedBroadcast receives the event, reacts and returns the result to calling component. sendOrderedBroadcast() method is used to send Ordered Broadcasts.  One of the arguments of sendOrderedBroacast() is result receiver or final receiver which is used to get the result in the calling component.

Main.xml :
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
     tools:context=".MainActivity" >

    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="sendOrederedEvent"
        android:text="Send Ordered Broadcast"
        android:textColor="@android:color/black"
        android:textSize="17sp"
        android:textStyle="bold" />
</RelativeLayout>

MainActivity.java:
public class MainActivity extends Activity {

private static final String EVENT_ACTION_NAME  = "com.smartmobsolutions.android.ordered_broadcastreceivers_demo_app";
           
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

public void sendOrederedEvent(View v){
 Intent i = new Intent(EVENT_ACTION_NAME);
 sendOrderedBroadcast(i, null, finalResultReceiver, null, Activity.RESULT_OK, null, null);
}
           
private BroadcastReceiver finalResultReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent){
            Toast.makeText(context, "Final Result Receiver",Toast.LENGTH_SHORT).show();
            Bundle results = getResultExtras(true);
            Toast.makeText(context, "Result = "+results.getString("result2"), Toast.LENGTH_SHORT).show();
                            
}
};
           
}


Register a receiver with priority. Give max priority to get it triggered first.

<receiver android:name=".Receiver1">
   <intent-filter android:priority="2">
   <action android:name="com.smartmobsolutions.android.ordered_broadcastreceivers_demo_app"/>
   </intent-filter>
</receiver>

And create below Receiver1.java in same app:

public class Receiver1 extends BroadcastReceiver{

            @Override
            public void onReceive(Context context, Intent intent) {
                        Toast.makeText(context, "Receiver1 triggered", Toast.LENGTH_SHORT).show();
                        Bundle results = getResultExtras(true);
                        results.putString("result1", "result in receiver1");

            }

}

Create another application with the Receiver of same event. Below is the Receiver class and registration of receiver class with low priority in manifest

public class Receiver2 extends BroadcastReceiver{

            @Override
            public void onReceive(Context context, Intent intent) {
                         Toast.makeText(context, "Receiver2 triggered", Toast.LENGTH_SHORT).show();
                         Bundle results = getResultExtras(true);
                         String resultInReceiver1 = results.getString("result1");
                 results.putString("result2", resultInReceiver1+", result in receiver2");
             }

}
<receiver android:name=".Receiver2">
   <intent-filter android:priority="1">
   <action android:name="com.smartmobsolutions.android.ordered_broadcastreceivers_demo_app"/>
    </intent-filter>
</receiver>

So, now we have two different applications with different receivers i.e Receiver1 with high priority in App1 and Receiver2 with low priority in App2. Both are intended to receive same action event. So When you click the button in App1, sendOrderedBroadcast() method will send an ordered broadcast to the receivers. Receiver1 will get triggered first and pass the result to its next receiver(s). Now the next receiver with max priority (i.e Receiver2) will get triggered and receive the result from previous receiver and pass the result to its next receivers if any with low priority, otherwise final receiver which is passed as an argument to sendOrderedBroadcast() method will get triggered.

We can even stop the broadcasting of events using abortBroadcast() method. i.e if we use abortBroadcast() method in Receiver1 class (which has max priority) then all other receivers who are registered for the same action (with lower priority) will not get triggered so the result will be passed directly from Receiver1 to final receiver in MainActivity.

This is how we can broadcast the events/intents in an ordered manner and pass the result back to calling component (i.e MainActivity in this example) .



Saturday, 1 March 2014

Understanding BROADCAST RECEIVERS in Android : PART 1


BROADCAST means simply an announcement of an event or something is happened. Broadcasting of announcements/messages can be done by 2 ways in Android.

1. By System: Android System can pass an announcement to all the installed applications saying something is happened. For ex: CONNECTIVITY_CHANGE is one of the announcements by Android system  i.e. whenever there is change in connection availability, system sends/passes an announcement to all the applications installed on device. So all the receivers (means applications) who are interested to respond to corresponding event will get triggered and reacts (More on this shortly). BATTERY_LOW, BOOT_COMPLETED are some of other examples of system broadcasts/announcements.

2. By User: User also can pass an announcement to all the installed applications saying something is happened. For ex: On click of a button OR on successful download of a file we can pass an announcement. So the receivers who are intended to receive this announcement can receive the message and react.

NOTE: All the receivers (I mean Applications) who are registered /intended to respond an announcement/event will receive it even when the apps are not running currently. So the receiver applications need not be running currently when the user/system broadcasts an announcement/event.

In PART 1, I’m going to discuss about the broadcasts By System.  Broadcasts By User in PART2.

Registering the Receiver :
In order to make your application receive an announcement passed by user/system you should register your application for that event. This registration can be done in 2 ways.

1.       In Manifest:  We can register a receiver  using  <receiver> tag for an event in manifest.
 For ex : You can register for CONNECTIVITY_CHANGE event as below.
    <receiver android:name=".MyConnectivityReceiver" >
    <intent-filter>
      <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
    </intent-filter>
  </receiver>

Here android.net.conn.CONNECTIVITY_CHANGE is name of the event which must be specified using intentfilters to react to that event. And MyConnectivityReciever is the receiver class which is going to take over the reacting job when the connectivity change event announced by the system.   

2.       In Activity:  We can also register a receiver dynamically in an activity using registerReceiver() method as below.  You need to create an object of your receiver class and pass as one of the arguments to the method.

IntentFilter iff= new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
registerReceiver(yourReceiverClassObject, iff);

 Generally IntentFilters are used to specify how the components (activities/services) of other applications that wish to invoke your component via a specified action. Here we used IntentFilters to specify the event name  i.e. Since Android system sends a broadcast with the action name android.net.conn.CONNECTIVITY_CHANGE when there is a change in connectivity state we must register  our receiver with the same action name in order to make your application respond to connectivitychange event. So now the question is What are the  action names of other events broadcasted by Android system? There are several system events available in the Intent class and other system classes like TelephonyManager.

Implementing the Receiver:  We can implement the receiver part by sub classing BroacastReceiver class and override onReceive() method.

public class MyConnectivityReceiver extends BroadcastReceiver{
      @Override
      public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,
        "Connectivity state changed", Toast.LENGTH_SHORT).show();
           
        //The task to do when the connectivity change happens.
      }
}
So, now our application is ready to receive the event of connectivity change broadcasted by Android system. So, whenever your device goes from online to offline and offline to online the onReceive() of your receiver class will get execute (even when your app is not running currently).

Unregistering the Receiver :  You can unregister the receiver using unregisterReceiver(yourReceiverClassObject) ; call in your activity. This call removes all the filters that have been registered for this receiver. So from now onwards your application is no more responsive for connectivitychange event.

Priority  to Receiver: Assume we have registered more than one application for the same event. When the Android system broadcasts an event, all the receiver apps that are registered to receive the event will trigger asynchronously. If you want to make your application receive the event first among all others then you can use priority like below  

   <receiver android:name=".MyConnectivityReceiver" >
   <intent-filter android:priority = “100”>
    <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
   </intent-filter>
 </receiver>

So the receiver whose priority is high will get triggered first among all others and the receiver without priority or least priority will get triggered last. If you are registering the receiver dynamically then you can set the priority as below.

IntentFilter iff = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
iff.setPriority(100);
registerReceiver(yourReceiverClassObject, iff); 

Generally the suggested place for registering the receiver in an activity is onResume() and for unregistering the receiver is onPause().