Sample Application - Part 2

 

Introduction

In this blog post, we will be talking about two additional features that can be fundamental in programming with Android. These two features will be Services and Broadcast Receivers. This blog post is part of a multipart series in which we cover various Android lessons. I would recommend taking a look at the  first blog post  as this will be extending the content from there.

Getting Started

In the last example we covered the basic Activity. The Activity is Android's version of a user facing application. A  Service  on the other hand, perform functions in the background and in many cases will not directly interact with the user at all. The Broadcast Receiver, which is part of the Activity, will replace the Handler from Part 1, and will be receiving the information from our Service. Once received, the Activity will display the information to the user.

Removing the Old

In order to begin this change, we need to understand what sections we are replacing. The Counter class will no longer be an Observable and the CounterUISample class will no longer be a Observer, instead it will be sending Broadcasts to any Broadcast Receivers that are listening for this specific event. Which also means that we no longer need a Handler to handle the message sent from the update method as there will no longer be an update method. Below are the changes to remove the Observer/Observable and Handler from our old code.

CounterUISample.java
 1
 2public class CounterUISample extends Activity  implements Observer  {
 3
 4    private Counter counter = null;
 5    private TextView textview = null;
 6     private Handler handler = null;
 7
 8    public CounterUISample() {
 9        super();
10        /*
11         * Create Counter 1 tick per second trigger every 10 seconds
12         */
13        this.counter = new Counter(1, 10);
14         counter.addObserver(this); // add this object as a Counter observer
15    }
16
17    /** Called when the activity is first created. */
18    @Override
19    public void onCreate(Bundle savedInstanceState) {
20        super.onCreate(savedInstanceState);
21        textview = new TextView(this);
22        textview.setText("Counter Sample");
23        setContentView(textview);
24         
25         // create new Handler and override handleMessage
26         handler = new Handler() {
27
28            @Override
29            public void handleMessage(Message msg) {
30                 if (textview != null) {
31                     String type = msg.getData().getString("type");
32                     Integer value = msg.getData().getInt("value");
33                     textview.setText("Counter Sample :" + type + ":"
34                             + String.valueOf(value));
35                     super.handleMessage(msg);
36                 }
37            }
38         };
39    }
40
41    @Override
42    protected void onStart() {
43        super.onStart();
44        counter.start();
45    }
46
47    @Override
48    protected void onStop() {
49        super.onStop();
50        counter.stop();
51    }
52
53     @Override
54     public void update(Observable observable, Object data) {
55         /*
56         * verify that the data is really of type CounterData, and log the
57         * details
58         */
59         if (data instanceof CounterData) {
60            CounterData counterdata = (CounterData) data;
61            Message message = new Message();
62            Bundle bundle = new Bundle();
63            bundle.putCharSequence("type", counterdata.type);
64            bundle.putInt("value", counterdata.value);
65            message.setData(bundle);
66            handler.sendMessage(message);
67            Log.i(counterdata.type, String.valueOf(counterdata.value));
68         }
69     }
70}

Counter.java
 1
 2public class Counter  extends Observable  {
 3    private Integer count = 0;
 4    private Integer tick = 0;
 5    private Integer trigger = 0; // trigger every N steps
 6
 7    CounterWorkerThread workerThread = new CounterWorkerThread();
 8     
 9    public Counter(Integer tick, Integer trigger) {
10        super();
11        this.count = 0;
12        this.tick = tick;
13        this.trigger = trigger;
14    }
15
16    public void stop() {
17        workerThread.stop();
18    }
19
20    public void start() {
21        workerThread.start();
22    }
23
24    private class CounterWorkerThread implements Runnable {
25        Thread thread = null;
26        boolean run = false;
27
28        CounterWorkerThread() {
29            thread = new Thread(this);
30        }
31
32        public void start() {
33            run = true;
34            if ( thread == null) {
35                thread = new Thread(this);
36            }
37            thread.start();
38        }
39
40        public void stop() {
41            run = false;
42            thread.interrupt();
43            thread = null;
44        }
45
46        public void run() {
47            try {
48                while (run == true) {
49                    Thread.sleep(tick * 1000);
50                    count++;
51                     setChanged();
52                    if ((count % trigger) == 0) {
53                        notifyObservers(new CounterData(CounterData.triggertype, count));
54                    } else {
55                        notifyObservers(new CounterData(CounterData.counttype,count));
56                    }
57                }
58            } catch (InterruptedException interruptEx) {
59                //do nothing here
60            } catch (Exception ex) {
61            
62                ex.printStackTrace();
63            }
64        }
65    }
66}

Broadcast Receiver

As you can see we removed all the places where we called the Handler and Observable/Observer. Now we need to change Counter so that it sends out Broadcasts that we can later receive with a Broadcast Receiver. In order to send Broadcasts, we need a context in which to send it from. This can be added by passing the context through the constructor to the Counter.

Constructor of Counter.java
 1
 2private Context context;
 3
 4public Counter(Integer tick, Integer trigger,  Context context) {
 5        super();
 6        this.count = 0;
 7        this.tick = tick;
 8        this.trigger = trigger;
 9         this.context = context;
10    }

Using this context we can send messages within the run method of the CounterWorkerThread. In order to send messages we actually send what are called  Intents. These Intents describe what its action is, or what you need to call in order to receive this Intent, and can contain information in a Bundle. A  Bundle  is just a group of information that can be stored together.  

Within the run method there is a few things that need to be added.

  • Line 8:  Create Intent
  • Lines 9, 12-13, 15-16, 18:  Create the Bundle and insert the data into the Intent
  • Line 19:  Give the Intent an action
  • Line 20:  Send out the Intent to any listeners
Run method from Counter.java
 1
 2public void run() {
 3    try {
 4        while (run == true) {
 5            Thread.sleep(tick * 1000);
 6            count++;
 7                     
 8            Intent broadcastIntent = new Intent();
 9            Bundle bundle = new Bundle();
10                     
11            if ((count % trigger) == 0) {
12                 bundle.putCharSequence("type", CounterData.triggertype);
13                 bundle.putInt("value", count);
14            } else {                        
15                 bundle.putCharSequence("type", CounterData.counttype);
16                 bundle.putInt("value", count);
17            }
18                 broadcastIntent.putExtras(bundle);
19                 broadcastIntent.setAction("com.cisco.sample.counter");
20                 context.sendBroadcast(broadcastIntent);
21            }
22    } catch (InterruptedException interruptEx) {
23        //do nothing here
24    } catch (Exception ex) {
25        ex.printStackTrace();
26    }
27}

The Counter is now sending out Broadcasts thus, we need a Broadcast Receiver to receive them. We need to edit the CounterUISample so that it registers with the given Broadcast Receiver and implements the onReceive method in order to make changes when it receives the message.

Within onStart in CounterUISample.java
 1IntentFilter in = new IntentFilter();
 2in.addAction("com.cisco.sample.counter");
 3broadcastReceiver = new BroadcastReceiver() {
 4    @Override
 5    public void onReceive(Context c, Intent in) {
 6        Bundle bundle = in.getExtras();
 7        String type = bundle.getString("type");
 8        Integer value = bundle.getInt("value");
 9        if (textview != null) {
10            textview.setText("Counter Sample: " + type + " "
11                    + String.valueOf(value));
12        }
13    }
14};
15registerReceiver(broadcastReceiver, in);

As you can see we make a new Broadcast Recevier which has its own onReceive method. In the onReceive method, we extract the information from the Intent. We then change the TextView to display the information from the Counter class. We also created an  Intent Filter  which will specify which Intent we are listening for. In this case, we are listening for the Action we provided above. Using this Broadcast Receiver and Intent Filter, we are able to register the receiver.  

The last change in order to make this Broadcast to be received by this Activity would be to modify the manifest. We add this in the application section of the manifest.

Within activity section of Android Manifest
1<intent-filter>
2    <action android:name="com.cisco.sample.counter"></action>
3</intent-filter>

This is just an additional Intent Filter added to this application that again has the same action. In the onStart and onStop methods we just need to start and stop the Counter.

Service

This is a good opportunity to show off a Service. The Counter is a background operation that should continue to count away regardless of what the User Interface is doing. In this way, the counter should be a Service. A Service in Android is similar to an Activity as it follows states in its operational lifetime but has its own distinct  lifecycle. We will be dealing with two specific states, onCreate and onDestroy.

We start by creating a new class called CounterService and having it extend Service. Service has one method that must be overwritten which is the onBind method.

CounterService.java  
1@Override
2public IBinder onBind(Intent intent) {
3    return null;
4}

We then instantiate a Counter and a constructor for CounterService. In the constructor we proceed to create a Counter.

CounterService.java
1private Counter counter = null;
2
3public CounterService() {
4    this.counter = new Counter(1, 10, this);
5}

In order for this Service to actual do something we need to overwrite the onCreate method. In this method we start the Counter. Additionally we need to stop the counter when the program is no longer running. We do this by overwriting the onDestroy method and having it stop the Counter.

CounterService.java
 1@Override
 2public void onCreate() {
 3    counter.start();
 4}
 5
 6@Override
 7public void onDestroy() {
 8    counter.stop();
 9}

We need to edit the manifest so that it knows that this Service exists.

Within the application section of Android Manifest
1<service android:name=".counterui.CounterService" ></service>

After making all of these changes we are able to run the program again. It should perform the same actions before but, instead of using a Observable/Observer relation and a Handler we are able to use Broadcast Receiver and Services. Although the additional Service adds no extra usability now, if we wanted to extended this program so that other applications on the Android could use this Service it would be much easier.

Conclusion

In this blog post, we were able to go over a couple of key things.

  • Services
  • Broadcast Receivers
  • Intents
  • Intent Filters
  • Bundles
All of these are valuable things to know when writing applications on an Android device.  
By MatthewT Williams

 

Subscribe to our Cius Developer Blog RSS feed to get the latest info and sample code.