Android Service - Tutorial

Version 2.8

29.12.2013

Revision History
Revision 0.1 - 2.807.03.2011 - 29.12.2013Lars
Vogel
bug fixed and enhancements

Developing own services and using system services in Android

This tutorial describes how to create and consume Android services. It is based on Eclipse 4.3, Java 1.6 and Android 4.4.

 

1. Android Services

1.1. What are services?

A service is a component which runs in the background without direct interaction with the user. As the service has no user interface, it is not bound to the lifecycle of an activity.

Services are used for repetitive and potentially long running operations, i.e., Internet downloads, checking for new data, data processing, updating content providers and the like.

Services run with a higher priority than inactive or invisible activities and therefore it is less likely that the Android system terminates them. Services can also be configured to be restarted if they get terminated by the Android system once sufficient system resources are available again.

It is possible to assign services the same priority as foreground activities. In this case it is required to have a visible notification active for the related service. It is frequently used for services which play videos or music.

1.2. Services and background processing

By default a service runs in the same process as the main thread of the application.

Therefore you need to use asynchronous processing in the service to perform resource intensive tasks in the background. A common used pattern for a service implementation is to create and run a new Thread in the service to perform the processing in the background and then to terminate the service once it has finished the processing.

Services which run in the process of the application are sometimes called local services.

1.3. Platform service and custom services

The Android platform provides and runs predefined system services and every Android application can use them, given the right permissions. These system services are usually exposed via a specific Manager class. Access to them can be gained via the getSystemService() method. The Context class defines several constants for accessing these services.

An Android application can, in addition to consuming the existing Android platform services, define and use new services. Defining your custom services allows you to design responsive applications. You can fetch the application data via it and once the application is started by the user, it can present fresh data to the user.

1.4. Starting and defining custom services

Custom services are started from other Android components, i.e., activities, broadcast receivers and other services.

2. Defining custom services

2.1. Implementation and declaration

A service needs to be declared in the AndroidManifest.xml file and the implementing class must extend the Service class or one of its subclasses.

The following code shows an example for a service declaration and its implementation.

<service
  android:name="MyService"
  android:icon="@drawable/icon"
  android:label="@string/service_name"
  >
</service> 

public class MyService extends Service {

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    //TODO do something useful
    return Service.START_NOT_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
  //TODO for communication return IBinder implementation
    return null;
  }
} 

2.2. Start a service

An Android component (service, receiver, activity) can trigger the execution of a service via the startService(intent) method.

// use this to start and trigger a service
Intent i= new Intent(context, MyService.class);
// potentially add data to the intent
i.putExtra("KEY1", "Value to be used by the service");
context.startService(i); 

Alternatively, you can also start a service via the bindService() method call. This allows you to communicate directly with the service. We discuss that later.

2.3. Service start process and execution

If the startService(intent) method is called and the service is not yet running, the service object is created and the onCreate() method of the service is called.

Once the service is started, the startService(intent) method in the service is called. It passes in the Intent object from the startService(intent) call.

If startService(intent) is called while the service is running, its onStartCommand() is also called. Therefore your service needs to be prepared that onStartCommand() can be called several times.

Tip

What if you call this method twice in your code? Do you have to worry about synchronizing the onStartCommand() method call? No, this method is called by the Android system in the main user interface thread, therefore it cannot be called simultaneously from two different threads.

A service is only started once, no matter how often you call the startService() method.

2.4. Service restart behavior

In its onStartCommand() method call, the service returns an int which defines its restart behavior in case the service gets terminated by the Android platform. You can use the constants, the most common options are described by the following table.

Table 1. Restart options

OptionDescription
Service.START_STICKYService is restarted if it gets terminated. Intent data passed to the onStartCommand method is null. Used for services which manages their own state and do not depend on the Intent data.
Service.START_NOT_STICKYService is not restarted. Used for services which are periodically triggered anyway. The service is only restarted if the runtime has pending startService() calls since the service termination.
Service.START_REDELIVER_INTENTSimilar to Service.START_STICKY but the original Intent is re-delivered to the onStartCommand method.


Tip

You can check if the service was restarted via the Intent.getFlags() method. START_FLAG_REDELIVERY (in case the service was started with Service.START_REDELIVER_INTENT) or START_FLAG_RETRY (in case the service was started with Service.START_STICKY) is passed.

2.5. Stopping a service

You stop a service via the stopService() method. No matter how frequently you called the startService(intent) method, one call to the stopService() method stops the service.

A service can terminate itself by calling the stopSelf() method. This is typically done if the service finishes its work.

3. Binding services

3.1. Binding to services from activities

If the activity wants to interact with the service directly, it can use the bindService() method to start the service.

This method requires a ServiceConnection object as a parameter which is called on the service start and when finishing its onBind() method. This method returns a IBinder object to the ServiceConnection.

This IBinder object can be used by the activity to communicate with the service.

When the binding process has finished, the onStartCommand() method in the service is called with the Intent object used for the bindService() method.

3.2. Local services bindings

If the service runs in the same process as the activity, it it possible to return the service to the activity. This allows that the activity can call methods of the service directly. This technique is demonstrated by Section 8, “Exercise: Define and consume local service”.

3.3. Interprocess communication services bindings

If the service run in its own process, you require IPC (Interprocess Communication) to communicate with the service. See Section 5.6, “AIDL for services in a different process”.

4. Services in separate processes

4.1. Running a service in its own process

You can also specify that your Service runs in a separate process via the android:process=":process_description" attribute.

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

The colon prefix in front of 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 Android applications.

Running a service in its own process will not block the application in case the service performs long running operations in its main thread. But as the services runs in its own process, you need to use some interprocess communication (IPC) to communicate to your service from other parts.

Even if the service runs in its own process, you need to use asynchronous processing to perform network access, because Android does not allow network access in the main thread of a process.

4.2. When to run a service in a separate process?

Running a service in its own process gives it its own memory address space and a garbage collector of the virtual machine in this process does not affect the application process.

Application rarely need to run a service in its own process. Running services in its own process makes the communication with other Android components and the service harder to implement.

Note

If you want to make a service to other Android application available, they must run in their own process.

5. Communicating with services

5.1. Options for communication

There are several possibilities for an activity to communicate with a service and vice versa. This section discusses the possible approaches and provides recommendation which to use.

5.2. Using Intent data

In a simple scenario no direct communication is required. The service receives the intent data from the starting Android component and performs its work. No notification is necessary. For example, in case the service updates a content provider, the activity is notified by the content provider and no extra step in the service is necessary. This approach works for local and services running in their own process.

5.3. Using receiver

You can also use broadcast events and registered receivers for the communication. For example, your activity can dynamically register a broadcast receiver for an event and the service sends outs corresponding events. This is a very typical scenario, in which the service need to signal to the activity that his processing has finished.

This communication flow is depicted in the following graphic.

Broadcast receiver used for service to activity communication

Tip

Android provides the LocalBroadcastManager class in the support library v4. This is a helper class to register for and send broadcasts of Intents to local objects within your process. This approach improves security as the broadcast events are only visible within your process and is faster then using standard events.

This approach works for local and services running in their own process.

5.4. Activity binding to local service

If the service is started in the same process as the activity, the activity can directly bind to the service. This is a relatively simple and efficient way to communicate and recommended for activities which need to have a fast communication layer with the service.

This approach works for local services.

5.5. Handler and ResultReceiver or Messenger

If the service should be communicating back to the activity, it can receive an object of type Messenger via the Intent data it receives from the activity. If the Messenger is bound to a Handler in the activity, the service can send objects of type Message to the activity.

A Messenger is parcelable, which means it can be passed to another process and you can use this object to send Messages to the Handler in the activity.

Messenger also provides the method getBinder() which allows to pass a Messenger to the activity. The activity can therefore send Messages to the service.

This approach works for local services running in their own process.

5.6. AIDL for services in a different process

To bind to a service which runs in a different process, you need to use Inter Process Communication (IPC) as the data needs to be sent between different processes. To do so, you need to create a AIDL file which looks similar to an Java interface, but ends with the .aidl file extension and is only allowed to extend other AIDL files.

This approach is required if you need to bind to a service running in another process, i.e., if your service is consumed by other Android applications.

You can find more information about this approach in the Android developer documentation about AIDL.

6. More on services

6.1. Starting services regularly via AlarmManager

As with activities the Android system may terminate the process of a service at any time to save resources. For this reason you cannot simple use a TimerTask in the service to ensure that it is executed on a regular basis.

For correct scheduling of the Service use the AlarmManager class. The following code demonstrates how to do this.

Calendar cal = Calendar.getInstance();

Intent intent = new Intent(this, MyService.class);
PendingIntent pintent = PendingIntent.getService(this, 0, intent, 0);

AlarmManager alarm = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
// Start every 30 seconds
alarm.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), 30*1000, pintent); 

6.2. IntentServices for one time tasks

You can also extend the IntentService class for your service implementation.

The IntentService is used to perform a certain task in the background. Once done, the instance of IntentService terminates itself automatically. An example for its usage would be downloading certain resources from the internet.

The IntentService class offers the onHandleIntent() method which will be asynchronously called by the Android system.

7. Exercise: Using services and service communication

The following example demonstrates how to use a service to download a file from the Internet based on a button click from an activity. Once done, the service notifies the activity via a broadcast receiver that the download is complete.

In this exercise you use the IntentService class, as this class provides automatic background processing.

Create a new project called com.vogella.android.service.receiver with the activity called MainActivity.

Create the following class for the service.

package com.vogella.android.service.receiver;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;

import android.app.Activity;
import android.app.IntentService;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;

public class DownloadService extends IntentService {

  private int result = Activity.RESULT_CANCELED;
  public static final String URL = "urlpath";
  public static final String FILENAME = "filename";
  public static final String FILEPATH = "filepath";
  public static final String RESULT = "result";
  public static final String NOTIFICATION = "com.vogella.android.service.receiver";

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

  // will be called asynchronously by Android
  @Override
  protected void onHandleIntent(Intent intent) {
    String urlPath = intent.getStringExtra(URL);
    String fileName = intent.getStringExtra(FILENAME);
    File output = new File(Environment.getExternalStorageDirectory(),
        fileName);
    if (output.exists()) {
      output.delete();
    }

    InputStream stream = null;
    FileOutputStream fos = null;
    try {

      URL url = new URL(urlPath);
      stream = url.openConnection().getInputStream();
      InputStreamReader reader = new InputStreamReader(stream);
      fos = new FileOutputStream(output.getPath());
      int next = -1;
      while ((next = reader.read()) != -1) {
        fos.write(next);
      }
      // successfully finished
      result = Activity.RESULT_OK;

    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      if (stream != null) {
        try {
          stream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      if (fos != null) {
        try {
          fos.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    publishResults(output.getAbsolutePath(), result);
  }

  private void publishResults(String outputPath, int result) {
    Intent intent = new Intent(NOTIFICATION);
    intent.putExtra(FILEPATH, outputPath);
    intent.putExtra(RESULT, result);
    sendBroadcast(intent);
  }
} 

Add this class to the AndroidManifest.xml file. Also add the permission to write to external storage and to access the Internet. The resulting AndroidManifest.xml file should look similar to the following listing.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.vogella.android.service.receiver"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="17"
        android:targetSdkVersion="18" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.vogella.android.service.receiver.MainActivity"
            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="com.vogella.android.service.receiver.DownloadService" >
        </service>
    </application>

</manifest> 

Change the layout file of your activity to the following.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="Download" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Status: " />

        <TextView
            android:id="@+id/status"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Not started" />
    </LinearLayout>

</LinearLayout> 

Change MainActivity to the following.

package com.vogella.android.service.receiver;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

  private TextView textView;
  private BroadcastReceiver receiver = new BroadcastReceiver() {

    @Override
    public void onReceive(Context context, Intent intent) {
      Bundle bundle = intent.getExtras();
      if (bundle != null) {
        String string = bundle.getString(DownloadService.FILEPATH);
        int resultCode = bundle.getInt(DownloadService.RESULT);
        if (resultCode == RESULT_OK) {
          Toast.makeText(MainActivity.this,
              "Download complete. Download URI: " + string,
              Toast.LENGTH_LONG).show();
          textView.setText("Download done");
        } else {
          Toast.makeText(MainActivity.this, "Download failed",
              Toast.LENGTH_LONG).show();
          textView.setText("Download failed");
        }
      }
    }
  };

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    textView = (TextView) findViewById(R.id.status);
    

  }

  @Override
  protected void onResume() {
    super.onResume();
    registerReceiver(receiver, new IntentFilter(DownloadService.NOTIFICATION));
  }
  @Override
  protected void onPause() {
    super.onPause();
    unregisterReceiver(receiver);
  }

  public void onClick(View view) {

    Intent intent = new Intent(this, DownloadService.class);
    // add infos for the service which file to download and where to store
    intent.putExtra(DownloadService.FILENAME, "index.html");
    intent.putExtra(DownloadService.URL,
        "http://www.vogella.com/index.html");
    startService(intent);
    textView.setText("Service started");
  }
} 

If you run your example and press the button, the download should be performed by the service. Once done, the user interface is updated and a Toast with the file name is shown.

Change the setting so that the service runs in its own process. Ensure that the application still works, as broadcast receivers are received across process boundaries.

8. Exercise: Define and consume local service

This exercise demonstrates how to bind to a local service from an activity.

The service is started after the Android device boots and simulates that it periodically fetches data. The activity binds itself to the service to access its data.

Create a new project called de.vogella.android.ownservice.local with the activity called MainActivity.

Create the following LocalWordService class.

package de.vogella.android.ownservice.local;

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

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

public class LocalWordService extends Service {
  private final IBinder mBinder = new MyBinder();
  private ArrayList<String> list = new ArrayList<String>();

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {

    Random random = new Random();
    if (random.nextBoolean()) {
      list.add("Linux");
    }
    if (random.nextBoolean()) {
      list.add("Android");
    }
    if (random.nextBoolean()) {
      list.add("iPhone");
    }
    if (random.nextBoolean()) {
      list.add("Windows7");
    }
    if (list.size() >= 20) {
      list.remove(0);
    }
    return Service.START_NOT_STICKY;
  }

  @Override
  public IBinder onBind(Intent arg0) {
    return mBinder;
  }

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

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

} 

Create the following two classes, which will be registered as BroadcastReceivers.

package de.vogella.android.ownservice.local;

import java.util.Calendar;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class MyScheduleReceiver extends BroadcastReceiver {

  // restart service every 30 seconds
  private static final long REPEAT_TIME = 1000 * 30;

  @Override
  public void onReceive(Context context, Intent intent) {
    AlarmManager service = (AlarmManager) context
        .getSystemService(Context.ALARM_SERVICE);
    Intent i = new Intent(context, MyStartServiceReceiver.class);
    PendingIntent pending = PendingIntent.getBroadcast(context, 0, i,
        PendingIntent.FLAG_CANCEL_CURRENT);
    Calendar cal = Calendar.getInstance();
    // start 30 seconds after boot completed
    cal.add(Calendar.SECOND, 30);
    // fetch every 30 seconds
    // InexactRepeating allows Android to optimize the energy consumption
    service.setInexactRepeating(AlarmManager.RTC_WAKEUP,
        cal.getTimeInMillis(), REPEAT_TIME, pending);

    // service.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(),
    // REPEAT_TIME, pending);

  }
} 

package de.vogella.android.ownservice.local;

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

public class MyStartServiceReceiver extends BroadcastReceiver {

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

Register all components in your AndroidManifest.xml file.

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

    <uses-sdk android:minSdkVersion="10" />

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

    <application
        android:icon="@drawable/icon"
        android:label="@string/app_name" >
        <activity
            android:name=".MainActivity"
            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=".LocalWordService"
            android:icon="@drawable/icon"
            android:label="@string/service_name" >
        </service>

        <receiver android:name="MyScheduleReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
        <receiver android:name="MyStartServiceReceiver" >
        </receiver>
    </application>

</manifest> 

Change the layout file of the activity similar to the following example.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="Update" > 
    </Button>

    <ListView
        android:id="@id/android:list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
    </ListView>

</LinearLayout> 

Change your activity class to the following code.

package de.vogella.android.ownservice.local;

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

import android.app.ListActivity;
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.Toast;

public class MainActivity extends ListActivity {
  private LocalWordService s;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    wordList = new ArrayList<String>();
    adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, android.R.id.text1,
        wordList);
  
    setListAdapter(adapter);
  }

  @Override
  protected void onResume() {
    super.onResume();
    Intent intent= new Intent(this, LocalWordService.class);
    bindService(intent, mConnection,
        Context.BIND_AUTO_CREATE);
  }

  @Override
  protected void onPause() {
    super.onPause();
    unbindService(mConnection);
  }

  private ServiceConnection mConnection = new ServiceConnection() {

    public void onServiceConnected(ComponentName className, 
        IBinder binder) {
      LocalWordService.MyBinder b = (LocalWordService.MyBinder) binder;
      s = b.getService();
      Toast.makeText(MainActivity.this, "Connected", Toast.LENGTH_SHORT)
          .show();
    }

    public void onServiceDisconnected(ComponentName className) {
      s = null;
    }
  };
  private ArrayAdapter<String> adapter;
  private List<String> wordList;

  public void onClick(View view) {
    if (s != null) {
      Toast.makeText(this, "Number of elements" + s.getWordList().size(),
          Toast.LENGTH_SHORT).show();
      wordList.clear();
      wordList.addAll(s.getWordList());
      adapter.notifyDataSetChanged();
    }
  }
} 

Run your application. If you press the button, the data is fetched again from the service and the ListView is updated.

9. Support free vogella tutorials

Maintaining high quality free online tutorials is a lot of work. Please support free tutorials by donating or by reporting typos and factual errors.

9.1. Thank you

Please consider a contribution if this article helped you.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值