7.4: Services

Contents:

In this chapter you learn about the different types of services, how to use them, and how to manage their lifecycles within your app.

What is a service?

A service is an app component that performs long-running operations, usually in the background. Unlike an Activity, a service doesn't provide a user interface (UI). Services are defined by the Service class or one of its subclasses.

A service can be started, bound, or both:

  • A started service is a service that an app component starts by calling startService().

    Use started services for tasks that run in the background to perform long-running operations. Also use started services for tasks that perform work for remote processes.

  • A bound service is a service that an app component binds to itself by calling bindService().

    Use bound services for tasks that another app component interacts with to perform interprocess communication (IPC). For example, a bound service might handle network transactions, perform file I/O, play music, or interact with a database.

A service runs in the main thread of its hosting process—the service doesn't create its own thread and doesn't run in a separate process unless you specify that it should.

If your service is going to do any CPU-intensive work or blocking operations (such as MP3 playback or networking), create a new thread within the service to do that work. By using a separate thread, you reduce the risk of the user seeing "application not responding" (ANR) errors, and the app's main thread can remain dedicated to user interaction with your activities.

In Android 8.0 (Oreo, API 26) or higher, the system imposes some new restrictions on running background services when the app itself isn't in the foreground. For details about these restrictions, see Background services and API 26.

To implement any kind of service in your app, do the following steps:

  1. Declare the service in the manifest.
  2. Extend a Service class such as IntentService and create implementation code, as described in Started services and Bound services, below.
  3. Manage the service lifecycle.

Declaring services in the manifest

As with activities and other components, you must declare all services in your Android manifest. To declare a service, add a <service> element as a child of the <application> element. For example:

<manifest ... >
  ...
  <application ... >
      <service android:name="ExampleService"
               android:exported="false" />
      ...
  </application>
</manifest>

To block access to a service from other apps, declare the service as private. To do this, set the android:exported attribute to false. This stops other apps from starting your service, even when they use an explicit intent.

Started services

How a service starts:

  1. An app component such as an Activity calls startService() and passes in an Intent. The Intent specifies the service and includes any data for the service to use.
  2. The system calls the service's onCreate() method and any other appropriate callbacks on the main thread. It's up to the service to implement these callbacks with the appropriate behavior, such as creating a secondary thread in which to work.
  3. The system calls the service's onStartCommand() method, passing in the Intent supplied by the client in step 1. (The client in this context is the app component that calls the service.)

Once started, a service can run in the background indefinitely, even if the component that started it is destroyed. Usually, a started service performs a single operation and does not return a result to the caller. For example, it might download or upload a file over the network. When the operation is done, the service should stop itself by calling stopSelf(), or another component can stop it by calling stopService().

For instance, suppose an Activity needs to save data to an online database. The Activity starts a companion service by passing an Intent to startService(). The service receives the intent in onStartCommand(), connects to the internet, and performs the database transaction. When the transaction is done, the service uses stopSelf() to stop itself and is destroyed. (This is an example of a service you want to run in a worker thread instead of the main thread.)

IntentService

Most started services don't need to handle multiple requests simultaneously, and if they did, it could be a complex and error-prone multi-threading scenario. For these reasons, the IntentService class is a useful subclass of Service on which to base your service:

  • IntentService automatically provides a worker thread to handle your Intent.
  • IntentService handles some of the boilerplate code that regular services need (such as starting and stopping the service).
  • IntentService can create a work queue that passes one intent at a time to your onHandleIntent() implementation, so you don't have to worry about multi-threading.
Note:IntentService is subject to the new restrictions on background services in Android 8.0 (API 26). For this reason, Android Support Library 26.0.0 introduces a new JobIntentService class, which provides the same functionality as IntentService but uses jobs instead of services when running on Android 8.0 or higher.

To implement IntentService:

  1. Provide a small constructor for the service.
  2. Create an implementation of onHandleIntent() to do the work that the client provides.

Here's an example implementation of IntentService:

public class HelloIntentService extends IntentService {
  /**
   * A constructor is required, and must call the 
   * super IntentService(String) constructor with a name 
   * for the worker thread.
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * The IntentService calls this method from the default 
   * worker thread with the intent that started the service. 
   * When this method returns, IntentService stops the service, 
   * as appropriate.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      try {
          Thread.sleep(5000);
      } catch (InterruptedException e) {
          // Restore interrupt status.
          Thread.currentThread().interrupt();
      }
  }
}

Bound services

A service is "bound" when an app component binds to it by calling bindService(). A bound service offers a client-server interface that allows components to interact with the service, send requests, and get results, sometimes using interprocess communication (IPC) to send and receive information across processes. A bound service runs only as long as another app component is bound to it. Multiple components can bind to the service at once, but when all of them unbind, the service is destroyed.

A bound service generally does not allow components to start it by calling startService().

Implementing a bound service

To implement a bound service, define the interface that specifies how a client can communicate with the service. This interface, which your service returns from the onBind() callback method, must be an implementation of IBinder.

To retrieve the IBinder interface, a client app component calls bindService(). Once the client receives the IBinder, the client interacts with the service through that interface.

There are multiple ways to implement a bound service, and the implementation is more complicated than a started service. For complete details about bound services, see Bound Services.

Binding to a service

To bind to a service that is declared in the manifest and implemented by an app component, use bindService() with an explicit Intent.

Caution: Do not use an implicit intent to bind to a service. Doing so is a security hazard, because you can't be certain what service will respond to your intent, and the user can't see which service starts. Beginning with Android 5.0 (API level 21), the system throws an exception if you call bindService() with an implicit Intent .

Service lifecycle

The lifecycle of a service is simpler than the Activity lifecycle. However, it's even more important that you pay close attention to how your service is created and destroyed. Because a service has no UI, services can continue to run in the background with no way for the user to know, even if the user switches to another app. This situation can potentially consume resources and drain the device battery.

Similar to an Activity, a service has lifecycle callback methods that you can implement to monitor changes in the service's state and perform work at the appropriate times. The following skeleton service implementation demonstrates each of the lifecycle methods:

public class ExampleService extends Service {
    // indicates how to behave if the service is killed.
    int mStartMode;  

    // interface for clients that bind.
    IBinder mBinder;

    // indicates whether onRebind should be used   
    boolean mAllowRebind; 

    @Override
    public void onCreate() {
        // The service is being created.
    }

    @Override
    public int onStartCommand(Intent intent, 
        int flags, int startId) {
        // The service is starting, due to a call to startService().
        return mStartMode;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService().
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return mAllowRebind;
    }

    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }

    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

Lifecycle of started services vs. bound services

A bound service exists only to serve the app component that's bound to it, so when no more components are bound to the service, the system destroys it. Bound services don't need to be explicitly stopped the way started services do (using stopService() or stopSelf()).

The diagram below shows a comparison between the started and bound service lifecycles.  Started and Bound Service Lifecycle, noborder

Foreground services

While most services run in the background, some run in the foreground. A foreground service is a service that the user is aware is running. Although both Activities and Services can be killed if the system is low on memory, a foreground service has priority over other resources.

For example, a music player that plays music from a service should be set to run in the foreground, because the user is aware of its operation. The notification in the status bar might indicate the current song and allow the user to launch an Activity to interact with the music player.

To request that a service run in the foreground, call startForeground() instead of startService(). This method takes two parameters: an integer that uniquely identifies the notification and the Notification object for the status bar notification. This notification is ongoing, meaning that it can't be dismissed. It stays in the status bar until the service is stopped or removed from the foreground.

For example:

Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent =
        PendingIntent.getActivity(this, 0, notificationIntent, 0);

Notification notification =
          new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
    .setContentTitle(getText(R.string.notification_title))
    .setContentText(getText(R.string.notification_message))
    .setSmallIcon(R.drawable.icon)
    .setContentIntent(pendingIntent)
    .setTicker(getText(R.string.ticker_text))
    .build();

startForeground(ONGOING_NOTIFICATION_ID, notification);
Note: The integer ID for the notification you give to startForeground() must not be 0.

To remove the service from the foreground, call stopForeground(). This method takes a boolean, indicating whether to remove the status bar notification. This method doesn't stop the service. However, if you stop the service while it's still running in the foreground, then the notification is also removed.

Background services and API 26

Services running in the background can consume device resources, potentially using device battery and resulting in a worse user experience. To mitigate this problem, the system now applies limitations on started services running in the background, beginning with Android version 8.0 (Oreo, API 26) or higher. These limitations don't affect foreground services or bound services.

Here are a few of the specific changes:

  • The startService() method now throws an IllegalStateException if an app targeting API 26 tries to use that method in a situation when it isn't permitted to create background services.
  • The new startForegroundService() method starts a foreground service from an app component. The system allows apps to call this method even while the app is in the background. However, the app must call that service's startForeground() method within five seconds after the service is created.

While an app is in the foreground, it can create and run both foreground and background services freely. When an app goes into the background, it has several minutes in which it is still allowed to create and use services. At the end of that time, the app is considered to be idle. The system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods.

Android 8.0 and higher does not allow a background app to create a background service. Android 8.0 introduces the new method startForegroundService() to start a new service in the foreground. After the system has created the service, the app has five seconds to call the service's startForeground() method to show the new service's user-visible notification. If the app does not call startForeground() within the time limit, the system stops the service and declares the app to be ANR (Application Not Responding).

For more information on these changes, see Background Execution Limits.

Scheduled services

For API level 21 and higher, you can launch services using the JobScheduler API, and with the background service restrictions in API 26 this may be a better alternative to services altogether. To use JobScheduler, you need to register jobs and specify their requirements for network and timing. The system schedules jobs for execution at appropriate times.

The JobScheduler interface provides many methods to define service-execution conditions. For details, see the JobScheduler reference.

Note: JobScheduler is only available on devices running API 21+, and is not available in the support library. If your app targets devices with earlier API levels, look into the backwards compatible WorkManager, a new API currently in alpha, that allows you to schedule background tasks that need guaranteed completion (regardless of whether the app process is around or not). WorkManager provides JobScheduler-like capabilities to API 14+ devices, even those without Google Play Services.

Learn more

results matching ""

    No results matching ""