Vaporstream Logo

Vaporstream Tech Blog

Mobile software development and operations

Concurrency Patterns in Android: The IntentService

by Andy Gibel

The following post covers the theoretical formulation and implementation of an Android IntentService that provides for the centralization and delegation of network calls that an application might make while communicating with a web service.

There are many forums and blogs that discuss the relative merits and uses of different tools such as the AsyncTask, Java Thread, Looper, Handler, Loader, and the list goes on. I propose that the following method produces one of the most scalable, modular, and elegant solutions possible given this particular problem space.

Stepping back for a moment, I’d like a chance to explain why this solution is worth blogging about at all.

If one’s world view were completely based on the field of Computer Science, they would likely be convinced that everything happens in a controlled and sequential manner. It’s a model that was created by Turing and others in an attempt to simplify the inherently complex and concurrent world we live in.

Concurrent programming has long been seen as an advanced topic or even as niche as in the field of supercomputing where machines try to make the most out of every cycle of an expensive CPU. Mobile devices change this landscape drastically as they require an increasing number of simultaneous tasks to be executed often taking advantage of multiple physical CPU cores.

Even in the context of a single process, care must be taken to delegate blocking or long running code to separate threads of execution. In the world of Android, not adhering to this rule will quickly land you in the painful world of the Application Not Responding (ANR) errors. The problem for Android neophytes is two fold. First, as mentioned, it is entirely likely that newer developers aren’t particularly comfortable with multi-threading to begin with. This is compounded by the second problem which is that the Android framework provides many useful tools to choose from. So many, in fact, that the difference and use cases for each is often a hot topic of debate and confusion.

What is the IntentService and why do I care?

As we know the Android Framework consists of four main components: the Activity, Service, ContentProvider, and Broadcast Receiver. While an Activity has a direct relationship to a visible screen in an application and is meant to be relatively short-lived, the Service has no direct affiliation with an Activity or screen. When there is a need for longer running background processes in an application, the Service is generally the weapon of choice because it can persist across Activity boundaries.

While the Service class can be subclassed directly, it is much more common to use the IntentService flavor due to several inherent benefits:

It launches its own work thread

An important fact that is often overlooked when using a Service is that they do not, by default, create a new thread. Therefore, if a regular Service is subclassed, all of its work will happen on the UI thread unless the implementor explicitly creates another. While this is perhaps a semantic difference, the IntentService handles this concern by automagically running all of its main loop work on a separate thread - correctly freeing up the UI thread and saving us from writing a fair amount of boiler plate code.

It terminates when the work in its main loop is finished

Services will persist from their creation until they are explicitly stopped. This can be error prone and can produce behaviors that are difficult to catch and debug. The IntentService relieves the caller from the responsibility by simply running the work it is given, and stopping itself.

Ease of implementation when using the Command pattern

A Service is generally started from an Activity. The communication channel between the caller Activity and Service is defined at creation and can use one of two patterns: Command or Bind.

The Command pattern is very simple to implement and understand. When a Service is started, an identifier is passed in along with an Intent. These respectively tell the Service what it should do, and pass the Service the data it needs to do that task. The Identifier is also later used by the calling Activity to determine what the Service was doing when it sends back the result.

To Bind with a Service is to set up a two-way API that can be used by the Activity and Service to communicate for the entire lifecycle. While this gives more control over how the two send messages, it is significantly more difficult to implement well especially when the bound Activity undergoes configuration changes. Since the IntentService simply runs, does its work, and returns a result, it makes it the perfect candidate for the Command pattern.

The code

For the purposes of this post, we will assume we only have three source files: The LoginActivity, the ActionService, and the SuccessActivity. We will simply display two Text fields and a button to a user so that they can log in. If the login is successful, the SuccessActivity comes up and signals that we have successfully logged in. If unsuccessful, we tell the user to get their act together and try again.

To let our Service have more than one possible action to take, we also provide a button called “Info” on the login screen that will ask the server for the newest info text. On Receipt, our Activity will display the info text in a label below all the other controls.

Architect your Service

All web calls and communications are now encapsulated in our subclass of IntentService. We simply check the incoming message from the calling Activity, act on the request, and return the result.

Create an Object inheriting from IntentService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class ActionService extends IntentService {
   /* Arbitrary int identifier.  Used to Identify the Service as the origin of returning
      results to the calling Activity. */
      public final static int ACTION_SERVICE_ID = 111;

   /* String and int constants used either by both the Action Service and caller Activity.  This
      defines the protocol by which we pass information back and forth between the two via 
      Intent extras. */
   public final static String PENDING_RESULT = "PENDING_RESULT";
   public final static String ACTION_TYPE = "ACTION_TYPE";
   public final static String EMAIL_ADDR = "email";
   public final static String PASSWORD = "password";
   public final static String INFO_TEXT = "info";
   public final static String RESULT_CODE = "RESULT_CODE";
   public final static String WORK_DONE = "WORK_DONE";
   public final static int CODE_OK = 0;
   public final static int CODE_ERROR = 1;

   /* All the types of actions this Service can perform */
      public enum ActionType {ACTION_LOGIN, ACTION_INFO, ACTION_NONE);

   public ActionService() {
      super("ActionService");
   }

   @Override
   protected void onHandleIntent(Intent incomingIntent) {
      /* From the Activity, we parse out what the caller wants the Service to do, and the 
         callback (PendingIntent) that we are to call when the work is done */
         ActionType workToDo = (ActionType)incomingIntent.getSerializableExtra(ACTION_TYPE, ACTION_NONE);
         PendingIntent pendingResult = incomingIntent.getParcelableExtra(PENDING_RESULT);

      /* The Intent that we will add the results to and send back to the caller */
      Intent returnIntent = new Intent();

      /* The result of this action - assume an error by default, change value on success */
      int workResultCode = CODE_ERROR;
  
      switch (workToDo) {
         case ACTION_LOGIN:  // we are logging in, parse the credentials for use in the web call
            String email = incomingIntent.getStringExtra(EMAIL_ADDR);
            String password = incomingIntent.getStringExtra(PASSWORD);
            workResultCode = sendLoginRequest(email, password);
            break;
         case ACTION_INFO:  // this is an info call, simply tell the server we want the info
            workResultCode = sendInfoRequest();

            /* In the case of this GET request, our info is returned from the server in the form of a JSON 
               String.  The details and error checking are left out here but after parsing the string and 
               confirming a successful request, we have access to the raw info via getInfoString() */
            returnIntent.putExtra(INFO_TEXT, myHttpClass.getJsonResponse.getInfoString());
            break;
         case ACTION_NONE:  // NOOP
            break;                  
         }

         try {
            /* inform the Activity which of the web service calls these results are for */
            returnIntent.putExtra(WORK_DONE, workToDo);

            /* Call back to the Activity with the result code and the data to parse if successful */
            pendingResult.send(this, workResultCode, returnIntent);
         } catch (PendingIntent.CanceledException e) {
            Log.e(getClass().getSimpleName(), "Pending intent was canceled", e);
         }
      }
   }

  /* The following two methods would call into your http classes to do the action POST or GET.  
           The actual implementation of these calls are out of scope for this particular post but may 
           be covered in the future.  All we care about for now is that they execute some call to our 
           server and return either 0 if successful for 1 if not.  Note that this implementation 
           calls for these methods to block this thread but it would not have to be done this way. */

   private int sendLoginRequest(String email, String password){
      return myHttpClass.doSendLoginRequest(email, password);
   }

   private int sendInfoRequest(){
      return myHttpClass.doSendInfoRequest();
   }
}

Call your Service and handle the return

Our main Activity is the LoginActivity. This is simply a screen presented to the user that collects both a username and password input. This information is packaged up and sent to our IntentService so that it may contact our web service for us. We make no direct networking calls in this class.

Call your Service when the login button or info button is pressed (in LoginActivity)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class LoginActivity extends Activity {
   @Override
   public void onCreate(Bundle savedInstanceState){
      super.onCreate(savedInstanceState);

      // Here we bind our XML Text fields and buttons.  

      // emailEditText - the edit text where users enter the email for login
      emailEditText = (EditText)findViewById(R.id.email);
      // passwordEditText - the edit text where users enter the password for login
      passwordEditText = (EditText)findViewById(R.id.password);
      // loginButton - the button users click to log in
      loginButton = (Button)findViewById(R.id.login);
      // infoButton - the button the user hits to get updated info from the server
      infoButton = (Button)findViewById(R.id.infoButton);
      // infoTextView - the text view where the info from the server will display
      infoTextView = (TextView)findViewById(R.id.info);

      loginButton.setOnClickListener(new OnClickListener(){
         @Override
         public void onClick(View v){
            Intent extras = new Intent(this, ActionService.class);
            extras.putExtra(ActionService.EMAIL_ADDR, emailEditText.getText().toString());
            extras.putExtra(ActionService.PASSWORD, passwordEditText.getText().toString());
            startActionService(ActionType.ACTION_LOGIN, extras);
         }
      }

      infoButton.setOnClickListener(new OnClickListener(){
         @Override
         public void onClick(View v){
            Intent extras = new Intent(this, ActionService.class);
            startActionService(ActionType.ACTION_INFO, extras);
         }
      }
   }
  
   private void startActionService(ActionType sendType, Intent extras) {

      /* create a pending intent callback for when we send back result 
         info from our Service to our Activity.  The Receipt of the 
         PendingIntent will go through onActivityResult just as if 
         we had called startActivityForResult */
      PendingIntent pendingResult = createPendingResult(
         ActionService.ACTION_SERVICE_ID,
         new Intent(this, LoginActivity.class),0);

      extras.putExtra(ActionService.ACTION_TYPE, sendType);
      extras.putExtra(ActionService.PENDING_RESULT, pendingResult);
      startService(extras);
   }

   /* Here is the entry point for the returning PendingIntent.

      -- requestCode = The identifier letting us know where these results are coming from.
      -- resultCode = The code telling us whether our webservce calls succeeded or failed.
      -- data = The bundled extras that we can parse our results from once we know the call succeeded.*/
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      if (requestCode == ActionService.ACTION_SERVICE_ID) {
         if (resultCode == CODE_OK) {
            ActionType actionPerformed = (ActionType) data.getSerializableExtra(ActionService.WORK_DONE);
            processServiceReturn(actionPerformed, data);
         } else {
            // here we could either fail silently or pop a dialog or Toast up informing the user there was a problem.
         }
      }
   }

   /* Finally, once we are here we know the service call succeeded and we can act accordingly. */
   private void processServiceReturn(ActionType action, Intent extras){
      switch(action){
         case ACTION_LOGIN:  // Login success!  Simply show the success Activity
            Intent successIntent = new Intent(this, SuccessActivity.class);
            startActivity(successIntent);
            finish();
            break;
         case ACTION_INFO:  // Got the info String, display it
            String info = data.getStringExtra(ActionService.INFO_TEXT);
            infoTextView.setText(info);
            break;
         case ACTION_NONE:  // NOOP
            break;
      }
   }
}

Summary and future work

While this setup may look complicated at first, I found it to be by far the cleanest of my reusable patterns. This example includes only two web calls but one can easily see that it scales very well - simply add additional cases and Action types for the Service to handle. I have used the PendingIntent callback in both these calls which serves us well when we are sitting at a particular Activity and wanting the result. If, however, there will be multiple Activites and you want the result no matter which one the user is on, you might consider Broadcast Receivers. This along with implementation of JSON marshalling/unmarshalling are among the candidates for future blog posts. Meanwhile, I hope to never catch any of you using messy, inline, anonymous AsyncTasks for web calls again!