Thursday, January 31, 2013

Android Processes

I've spent too many hours trying to understand the mechanic of processes in android; it's not difficult but it takes a while to see how to manage them, and pass information between them and activities; so I made this tutorial in order to clarify the very basics of processes:
  1. Start a process.
  2. Send data from activity to a process.
  3. Send data from process to activity.

Create a project in Eclipse

  • Application name: ProcessTest
  • Project name: ProcessTest
  • Package name: com.reprom.processtest
  • Min SDK: API 10
  • Target SDK: API 17
  • Compile with: API 10
  • Theme: None
Leave the rest of the settings to default values, and create one activity called MainActivity (Note: the activity layers in this tutorial are not important because we are going to work directly with logs, and watch the results in LogCat, so you can leave the default layer "activity_main" without changes).

Service class

Quoting the android developers official documentation "A Service is an application component representing either an application's desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use.", so, to say it simple, a service is a manner of performing operations outside our app, it means that if our app is opening or closing, that not affect the flux of our service (unless explicitly we close it). An example of that would be an app that needs to update some data with some server, and it's managed by a service (the service check for updates, no mather is the app is open or not), in that example, the app is not overloaded with such long-term operations; the user interface doesn't slow down, and when it's needed, the service notices the app with the correspondent information.
In this tutorial I'll show you how to create a service that runs a count-down timer in background, and notices our MainActivity ever 2 seconds and finally, when it's finished. First, create a class TestService, a service class must inherit from android.app.Service, put the following code:
package com.reprom.processtest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class TestService extends Service {

private final String TAG = "Service";
    Intent broadcastIntent = new Intent("testService"); 

 @Override
 public IBinder onBind(Intent arg0) {
  // TODO Auto-generated method stub
  return null;
 }

}
The method onBind must be overriden, but in this tutorial we are not going to talk about it. The TAG var I added is for identifying our logs, and broadcastIntent is an Intent object we'll need to broadcast data to our activity. Now, add an onCreate method to watch when our service is starting:
@Override
    public void onCreate() {
     super.onCreate();
        Log.d(TAG, "Service onCreate() running...");
    }
Now, we are going to see the "quid" of all this, the method onStartCommand(); which is invoked every time Context.startService() is called from an activity; performing the proper operations that the service is intended to do. Look at the code:
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
     Bundle bundle = intent.getExtras();
     
     new CountDownTimer(
       bundle.getLong("millisUntilFinished"),
       bundle.getLong("countDownInterval")) {      

      public void onTick(long millisUntilFinished) {
        Log.d(TAG, "onTick from TestService");
        broadcastIntent.putExtra("action", "timerTick");
           broadcastIntent.putExtra("millisUntilFinished", millisUntilFinished);
           sendBroadcast(broadcastIntent);
      }
      
      public void onFinish() {
           Log.d(TAG, "onFinish from TestService");
           broadcastIntent.putExtra("action", "timerFinish");
           sendBroadcast(broadcastIntent);
         }
       }.start();

        return START_STICKY;
    }
First we retrieve the data that comes from the activity in a Bundle object, then create a new CountDownTimer, passing the time in milliseconds until it finishes and the interval time (onTick() will be called every interval). On ever onTick() we put the time left in our broadcastIntent object, and put a variable called "action" to notify the activity what is the kind of broadcasting it's receiving. In method onFinish() we do the same; then, sendBroadcast() will broadcast the message. (Note: I am not going to explain in this basic tutorial about the return value (Service.START_STICKY), see the android documentation for that.
The service won't start if we don't register it in the AndroidManifest file, inside "Application" tag:

        
Note the colon sign in process, it means that the process is private for our app and not shareable.

The activity

Ok, now we have the service; let's fill the activity:
package com.reprom.processtest;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;

public class MainActivity extends Activity {
 
 private final static String TAG = "MainActivity";
 private Intent serviceIntent;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  serviceIntent = new Intent(this.getApplicationContext(), TestService.class); 
     serviceIntent.putExtra("millisUntilFinished", 10000l); 
     serviceIntent.putExtra("countDownInterval", 2000l); 
     startService(serviceIntent);
 }

}
Declare a serviceIntent Intent which we'll use to send data to process. I onCreate() we put the milliseconds until the count-down finishes and the interval rate in a long type; and then call startService() with the Intent to star the service.
Create a BroadcastReceiver object in order to read the broadcast sent from service:
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
   
         @Override
         public void onReceive(Context context, Intent intent) {
          Bundle bundle = intent.getExtras();
          
          switch (bundle.getInt("action")){
          case 0:
           Log.d(TAG, "Time to finish: "+bundle.getLong("millisUntilFinished"));
           break;
          case 1:
           Log.d(TAG, "Timer finished.");
           break;
          default:
          }
         }
     };
Finally we need to register the BroadcastReceiver object in onResume(), and unnregister in onPause():
@Override
 public void onResume() {
  super.onResume();  
  registerReceiver(broadcastReceiver, new IntentFilter("testService"));
 }
 
 @Override
 public void onPause() {
  super.onPause();
  unregisterReceiver(broadcastReceiver);
 }