Tuesday, 29 November 2011

Android Service and Broadcast Receiver


1. Android Services

1.1. Service

Service is a component which runs in the background, without interacting with the user. Every developer can create new Services in his application. Services support true multitasking for Android, as they can run in their own process. If you use threads in Activities their are still connected to the life-cycle of Activities and the Android system may decide to terminate them at any point in point.
The Android platform provides pre-defined services, usually exposed via a specific Manager class. Access to these services can be gained via the method getSystemService() .
Own services must be declared via the AndroidManifest.xml of the corresponding application.

1.2. Declaring own Services

You can declare your own service to perform long running operations without user interaction or to supply functionality to other applications.
Service needs to be declared in the AndroidManifest.xml via a <service android:name="yourclasss"> </service> and the implementing class must extend the class Serviceor one of its subclasses.
You can also specify that your Service should run in a different process then your application via theandroid:process=":process_description" attribute. This way the service gets its own process and has its own memory. Therefore any low running operation in the Service , e.g. a garbage collection, will not affect the user interface.
The colon prefix before the name tells Android that the Service is private to its declaring application. If the colon is not used the Service would be a global process and can be used by other components.

<service
 android:name="WordService"
 android:process=":my_process" 
 android:icon="@drawable/icon"
 android:label="@string/service_name"
 >
</service>
   

Service will not automatically run in its own thread. Without the process attribute they run the main thread of their hosting process. Therefore you should run performance intensive tasks in the background.

1.3. Starting Services

An Activity can start a Service via the startService() method and stop the service via thestopService() method. If the Activity want to interact with the Activity it can use the bindService()method of the service. This requires an ServiceConnection object which allows to connect to the Serviceand which return a IBinder object. This IBinder object can be used by the activity to communicate with the Service .
Once a Service is started the onCreate() method is called. Afterwards the onStartCommand() method is called with the Intent data provided by the activity.
startService() also allows you to provide a flag which determines the lifecycle behavior of the services.START_STICKY is used for services which are explicit started or stopped. Services started withSTART_NOT_STICKY will end automatically after the onStartCommand() method is done. A Service is started within the main thread of the application therefore all long running tasks should be performed in the background.
One common case is that the Service and the Activity are very closely related. In this case class just addIBinder to the class the Service provides. See Local Service Example for an example.
Another example would be the usage of Remote Messenger Service. In this case Activities can send messages to the Services and receive messages as well if they register for them.

1.4. Automatically starting Services

To start Services automatically after the Android system starts you can register a BroadcastReceiver to the Android android.intent.action.BOOT_COMPLETED system event.
In the onReceive() method the corresponding BroadcastReceiver would then start the service.

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class MyReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
  Intent service = new Intent(context, WordService.class);
  context.startService(service);
 }
}
   

If you application is installed on the SD card, then it is not available after theandroid.intent.action.BOOT_COMPLETED event. Register yourself in this case for theandroid.intent.action.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE event.
Also note that as of Android 3.0 the user needs to have started the application at least once before your application can receive android.intent.action.BOOT_COMPLETED events.

2. Broadcast receiver and Pending Intent

2.1. Broadcast Receiver

A broadcast receiver is a class which extends BroadcastReceiver and which is registered as a receiver in an Android Application via the AndroidManifest.xml file(or via code).
This class will be able to receive intents. Intents can be generated via the Context.sendBroadcast() method.
The class BroadcastReceiver defines the method onReceive() . Only during this method your broadcast receiver object will be valid, afterwards the Android system will consider your object as no longer active. Therefore you cannot perform any asynchronous operation.

2.2. Pending Intent

This tutorial will also use a PendingIntent. A PendingIntent is a token that you give to another application (e.g. Notification Manager, Alarm Manager or other 3rd party applications), which allows this other application to use the permissions of your application to execute a predefined piece of code.
To perform a broadcast via a pending intent so get a PendingIntent via PendingIntent.getBroadcast() . To perform an activity via an pending intent you receive the activity via PendingIntent.getActivity() .

3. Broadcast Receiver Example

We will define a broadcast receiver which listens to telephone state changes. If the phone receives a phone call then our receiver will be notified and log a message.
Create a new project "de.vogella.android.receiver.phone". We do not need an activity. Create the following "AndroidManifest.xml".

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="de.vogella.android.receiver.phone" android:versionCode="1"
 android:versionName="1.0">
 <application android:icon="@drawable/icon" android:label="@string/app_name">


  <receiver android:name="MyPhoneReceiver">
   <intent-filter>
    <action android:name="android.intent.action.PHONE_STATE"></action>
   </intent-filter>
  </receiver>
 </application>
 <uses-sdk android:minSdkVersion="9" />


 <uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
</manifest> 
  

Create the following class "MyPhoneReceiver".

package de.vogella.android.receiver.phone;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.util.Log;

public class MyPhoneReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
  Bundle extras = intent.getExtras();
  if (extras != null) {
   String state = extras.getString(TelephonyManager.EXTRA_STATE);
   Log.w("DEBUG", state);
   if (state.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
    String phoneNumber = extras
      .getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
    Log.w("DEBUG", phoneNumber);
   }
  }
 }
}

  

If you install your application and receive a call, e.g simulated by the DDMS perspective in Eclipse, then your receiver will be called and lot a message to the console.

4. System Services and Broadcast Receiver

In this chapter we will use the AlertManager and VibratorManager. The VibratorManager will be called by the broadcast receiver which will be called by the AlertManager.
Create a new project "de.vogella.android.alarm" with the activity "AlarmActivity". Create the following layout.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical" android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 <EditText android:layout_height="wrap_content" android:id="@+id/time"
  android:layout_width="wrap_content" android:hint="Number of seconds"
  android:inputType="numberDecimal">></EditText>
 <Button android:text="Start Counter" android:id="@+id/ok"
  android:onClick="startAlert" android:layout_width="wrap_content"
  android:layout_height="wrap_content"></Button>
</LinearLayout>

   

Create the following broadcast receiver class. This class will get the Vibrator service.

package de.vogella.android.alarm;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Vibrator;
import android.widget.Toast;

public class MyBroadcastReceiver extends BroadcastReceiver {
 @Override
 public void onReceive(Context context, Intent intent) {
  Toast.makeText(context, "Don't panik but your time is up!!!!.",
    Toast.LENGTH_LONG).show();
  // Vibrate the mobile phone
  Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
  vibrator.vibrate(2000);
 }

}

   

Maintain this class as broadcast receiver in "AndroidManifest.xml" and allow the vibrate authorization.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="de.vogella.android.alarm" android:versionCode="1"
 android:versionName="1.0">
 <uses-sdk android:minSdkVersion="9" />

 <application android:icon="@drawable/icon" android:label="@string/app_name">
  <activity android:name=".AlarmActivity" android:label="@string/app_name">
   <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
  </activity>
  <receiver android:name="MyBroadcastReceiver"></receiver>

 </application>
 <uses-permission android:name="android.permission.VIBRATE"></uses-permission>
 
</manifest>
   

Change the code of your Activity "AlarmActivity" to the following. This activity will create an Intent for the Broadcast receiver and get the AlarmManager service.

package de.vogella.android.alarm;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class AlarmActivity extends Activity {
 
/** Called when the activity is first created. */
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } public void startAlert(View view) { EditText text = (EditText) findViewById(R.id.time); int i = Integer.parseInt(text.getText().toString()); Intent intent = new Intent(this, MyBroadcastReceiver.class); PendingIntent pendingIntent = PendingIntent.getBroadcast( this.getApplicationContext(), 234324243, intent, 0); AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (i * 1000), pendingIntent); Toast.makeText(this, "Alarm set in " + i + " seconds", Toast.LENGTH_LONG).show(); } }

Run your application on the device. Set your time and start the alarm. After the defined number of seconds a Toast should be displayed. The vibrator alarm does not work on the simulator.

5. Define and consume your own service

The following will demonstrate how to create and consume a service from an activity. The service will periodically fetch data. The service will used by an activity which bind itself to the service. The activity will allow to request the latest data from the service.
Create a new project "de.vogella.android.ownservice" with the activity "ServiceConsumer".
Create a service "WordService" by create the class and the entry in "AndroidManifest.xml".

package de.vogella.android.ownservice;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class WordService extends Service {
 private Timer timer = new Timer();
 private static final long UPDATE_INTERVAL = 5000;
 private final IBinder mBinder = new MyBinder();
 private ArrayList<String> list = new ArrayList<String>();
 private String[] fixedList = { "Linux", "Android", "iPhone", "vogella.de",
   "helpful", "stuff" };
 private int index = 0;

 public void onCreate() {
  super.onCreate();
  pollForUpdates();
 }

 private void pollForUpdates() {
  timer.scheduleAtFixedRate(new TimerTask() {
   @Override
   public void run() {
    // Imagine here a freaking cool web access ;-)
    if (list.size() >= 6) {
     list.remove(0);
    }
    list.add(fixedList[index++]);
    if (index >= fixedList.length) {
     index = 0;
    }
   }
  }, 0, UPDATE_INTERVAL);
  Log.i(getClass().getSimpleName(), "Timer started.");

 }

 @Override
 public void onDestroy() {
  super.onDestroy();
  if (timer != null) {
   timer.cancel();
  }
  Log.i(getClass().getSimpleName(), "Timer stopped.");

 }

 // We return the binder class upon a call of bindService
 @Override
 public IBinder onBind(Intent arg0) {
  return mBinder;
 }

 public class MyBinder extends Binder {
  WordService getService() {
   return WordService.this;
  }
 }

 public List<String> getWordList() {
  return list;
 }

}

  


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="de.vogella.android.ownservice"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="10" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".ServiceConsumer"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name="WordService"></service>

    </application>
</manifest>
  

Change the layout "main.xml" to the following.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical" android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 <Button android:text="Reload Data" android:id="@+id/button1"
  android:layout_width="wrap_content" android:layout_height="wrap_content"
  android:onClick="showServiceData"></Button>
 <ListView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent"></ListView>
</LinearLayout>

   

Change the activity ServiceConsumer to the following.

package de.vogella.android.ownservice;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class ServiceConsumer extends Activity {
 private WordService s;
 private ArrayList<String> values;

 
/** Called when the activity is first created. */
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); doBindService(); values = new ArrayList<String>(); adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, values); ListView list = (ListView) findViewById(R.id.list); list.setAdapter(adapter); // List<String> wordList = s.getWordList(); // Toast.makeText(this, wordList.get(0), Toast.LENGTH_LONG).show(); } private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder binder) { s = ((WordService.MyBinder) binder).getService(); Toast.makeText(ServiceConsumer.this, "Connected", Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { s = null; } }; private ArrayAdapter<String> adapter; void doBindService() { bindService(new Intent(this, WordService.class), mConnection, Context.BIND_AUTO_CREATE); } public void showServiceData(View view) { if (s != null) { List<String> wordList = s.getWordList(); values.clear(); values.addAll(wordList); adapter.notifyDataSetChanged(); } } }