time

How to use services in Android for background tasks

Introduction to services

Android Services are processes that run in the background and do not have a user interface. They can be started and subsequently managed from Activities, Broadcast Receivers or other Services. Android Services are ideal for situations where an application needs to continue performing tasks but does not necessarily need a user interface to be visible to the user. Although Services lack a user interface, they can still notify the user of events through the use of notifications and toasts and are also able to issue Intents.

Android service is used to perform tasks in the background without user interaction. Android service by default runs in the main thread. If you have background task that takes long time to complete, it is better to create a thread within the service to perform long running tasks so that user operations are not blocked.

Unlike Broadcast Receivers, which are intended to perform a task quickly and then exit, services are designed to perform tasks that take a long time to complete (such as downloading a file over an internet connection or streaming music to the user) but do not require a user interface.

Services are given a higher priority by the Android runtime than many other processes and will only be terminated as a last resort by the system in order to free up resources.

Using a Service we can implement multitask in Android. Example situations where a Service might be a practical solution include:

  • Implement multi-task (downloads of file, perform non-user input requiring I/O operations like backup, etc.)
  • Enable Inter-Process-Communication (IPC) (handle network transactions, play audio/music in background, etc.)

Remember that a service is not bound to the Activity and cannot modify views within the UI directly. Instead, a service tends to have very specific outputs after running that are not directly associated with the UI. The outputs created by services are notifications (creates a dashboard notification to alert the user), broadcasts (triggers a broadcast message to be received), SQLite (write data received into the local database), files (cache blob data such as images or json to file), shared preferences (save key-values to shared preferences).

There are different types of services

  • Started services
  • Intent services
  • Bound services

Started services are launched by other application components (such as an activity or even a broadcast receiver) and potentially run indefinitely in the background until the service is stopped, or is destroyed by the Android runtime system in order to free up resources. A service will continue to run if the application that started it is no longer in the foreground, and even in the event that the component that originally started the service is destroyed.

Started service runs in the background until it is stopped and doesn’t return results to the caller.

By default, a service will run within the same main thread as the application process from which it was launched. It is important that any CPU intensive tasks be performed in a new thread within the service.

Started services are launched via a call to the startService() method, passing through as an argument an Intent object identifying the service to be started. When a started service has completed its tasks, it should stop itself via a call to stopSelf(). Alternatively, a running service may be stopped by another component via a call to the stopService() method, passing through as an argument the matching Intent for the service to be stopped.

Services are given a high priority by the Android system and are typically amongst the last to be terminated in order to free up resources.

The IntentService class is a convenience class (subclassed from the Service class) that sets up a worker thread for handling background tasks and handles each request in an asynchronous manner. Once the service has handled all queued requests, it simply exits. All that is required when using the IntentService class is that the onHandleIntent() method be implemented containing the code to be executed for each request.

For services that do not require synchronous processing of requests, IntentService is the recommended option.

A bound service is similar to a started service with the exception that a started service does not generally return results or permit interaction with the component that launched it. A bound service, on the other hand, allows the launching component to interact with, and receive results from, the service.

Bound service allows callers to interact with it using the provided client-server interface. Bound service stops when all clients are unbind.

A component (also referred to in this context as a client) starts and binds to a bound service via a call to the bindService() method and multiple components may bind to a service simultaneously. When the service binding is no longer required by a client, a call should be made to the unbindService() method. When the last bound client unbinds from a service, the service will be terminated by the Android runtime system. It is important to keep in mind that a bound service may also be started via call to startService(). Once started, components may then bind to it via bindService() calls. When a bound service is launched via a call to startService() it will continue to run even after the last client unbinds from it.

A bound service must include an implementation of the onBind() method which is called both when the service is initially created and when other clients subsequently bind to the running service.

Local and Remote Service

Components (activities, broadcast receivers or other services) start a Service through intents. These components are also referred to as client components sometimes. Now this invocation can happen in three ways:

  • Local Service – both the component and service of the application runs in the same process.
  • Private Remote Service – the service runs in a different process from the component but only accessible to components that belongs to only that particular application.
  • Global Remote Service – pretty much similar to Private Remote Services but also accessible to other applications, not through the class name (as they won’t know it) but through Intent Filters.

Life cycle of a Service

A service has life cycle callback methods that you can implement to monitor changes in the service's state and you can perform work at the appropriate stage. The following diagram on the left shows the life cycle when the service is created with startService() and the diagram on the right shows the life cycle when the service is created with bindService()

Android activity life cycle

A service must be created as a subclass of the Android Service class. As part of the subclassing procedure, one or more of the following superclass callback methods must be overridden, depending on the exact nature of the service being created:

  • onStartCommand() method is called when the service is started by another component via a call to the startService() method. This method does not need to be implemented for bound services.
  • onBind() methid is called when a component binds to the service via a call to the bindService() method. When implementing a bound service, this method must return an IBinder object facilitating communication with the client. In the case of started services, this method must be implemented to return a NULL value.
  • onCreate() method is a location to perform initialization tasks, this method is called immediately before the call to either onStartCommand() or the first call to the onBind() method.
  • onDestroy() method is called when the service is being destroyed.
  • onHandleIntent() method is applies only to IntentService subclasses. This method is called to handle the processing for the service. It is executed in a separate thread from the main application.

An android Service can be started from an Activity, from a Broadcast receiver and other services too. To start a service, startService() or bindService() need to be called. Starting a service with startService() creates started service meaning it runs until stopSelf() or stopService() method is called. Starting a service with bindService() creates bound service and it returns client-server interface for clients to interact with the service.

In order for a service to be useable, it must first be declared within a manifest file. This involves embedding an appropriately configured <service> element into an existing <application> entry. At a minimum, the <service> element must contain a property declaring the class name of the service. By default, services are declared as public, in that they can be accessed by components outside of the application package in which they reside.

Example of started service

Started services are those that are launched by other application components like an Activity or a Broadcast Receiver. They can run indefinitely in the background until stopped or destroyed by the system to free up resources.

We are going to create a custom service. Right-click the /src/java/ folder on the Project Explorer and select New -> Service -> Service. Leave the default values unchanged and click Finish to create the Service class.

We will implement the other callbacks. We will also add logging to understand the order of the calls.

public class MyService extends Service {
   public MyService() {}

   @Override
   public IBinder onBind(Intent intent) {
      Log.d(TAG, "onBind callback called");
      throw new UnsupportedOperationException("Not yet implemented");
   }

   @Override
   public int onStartCommand(Intent intent, int flags, int startId) {
      Log.d(TAG, "onStartCommand callback called");
      return super.onStartCommand(intent, flags, startId);
   }

   @Override
   public void onCreate() {
      super.onCreate();
      Log.d(TAG, "onCreate callback called");
   }

   @Override
   public void onDestroy() {
      super.onDestroy();
      Log.d(TAG, "onDestroy callback called");
   }
}

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.

  • Service.START_STICKY. Service 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_STICKY. Service 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_INTENT. Similar to Service.START_STICKY but the original Intent is re-delivered to the onStartCommand method.

If our task is hard for UI thread we can create new thread via following snippet. Always write your long running tasks in a separate thread, to avoid ANR.

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d(TAG, "onStartCommand callback called");
    new Thread(new Runnable() {
        @Override
        public void run() {
            // your logic that service will perform will be placed here

            // stop service once it finishes its task
            // stopSelf();
        }
    }).start();

    return super.onStartCommand(intent, flags, startId);
}

Finally, we will add a couple of buttons on MainActivity. When the user clicks the buttons, we will invoke the services we have created above.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_service"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="me.proft.sandbox.ServiceActivity">

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:orientation="horizontal"
        android:weightSum="2">

        <Button
            android:id="@+id/btnStart"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Start"
            android:onClick="startMyService" />

        <Button
            android:id="@+id/btnUpdate"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Stop"
            android:onClick="stopMyService" />
    </LinearLayout>
</RelativeLayout>

Now that we’re set with our service/, it’s time to see how to trigger its execution. Here’s how a service* is started:

public class ServiceActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_service);
    }

    void startMyService (View v ) {
        Intent i = new Intent(this, MyService.class);
        startService(i);
    }


    void stopMyService (View v ) {
        Intent i = new Intent(this, MyService.class);
        stopService(i);
    }
}

If the service is not yet running, then startService() triggers the onCreate() method of the service first. Once the service is started (or it is already running), then the onStartCommand() is called with the Intent object passed to startService(). Calls to startService() are non-blocking even if some processing has to be done in the Service class.

If you want to stop the service, then just call stopService() once:

// create the same explicit Intent
Intent i = new Intent(this, MyService.class);
// stop the service
stopService(i);

The service can also stop itself by calling stopSelf() when it finishes its work.

Example of intent service

As mentioned before a started service runs on the main thread, so we have to be very careful when we implement some logic in this service. We have to consider that if this logic is a blocking operation or it requires long time to finish a ANR problem could occur. In this case we have to move our logic to a separate thread, meaning we have to create a thread in onStartCommand method and run it.

There is another class called IntentService derived from Service that simplify our life. This class is useful when we don’t need to handle multiple requests at the same time. This class creates a worker thread to handle different requests. This class performs this operation:

  • Create a separate thread to handle the request.
  • Create a request queue and pass one Intent at time.
  • Create a default implementation of onStartCommand.
  • Stop the service when all the requests are processed.

A few limitations of an IntentService to be aware of:

  • You cannot affect the user interface from this background service directly.
  • Requests are handled on a single worker thread and processes just one request at a time.
  • You cannot easily cancel an intent service once you start one.

If we want to create a IntentService we have to extend IntentService class instead of Service. In this case, we have only one method to implement called onHandleIntent. Here we implement out logic without caring if this is operation requires long time or not, because this method is called in a separate thread.

Let’s just simulate a long action that takes 10 seconds

public class LongTaskIntentService extends IntentService {
    public LongTaskIntentService() {
        super("LongTaskIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
       showToast("Starting long task ...");
       try {
           Thread.sleep(10000);
       } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
       }
       showToast("Long task finished");
    }

    protected void showToast(final String msg){
        Handler handler = new Handler(Looper.getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                // run this code in the main thread
                Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
            }
        });
    }
}

The service may not be running in the same UI context, so to show the toast (interact with the Android UI) we may need to run it on the same UI thread. We do it in the showToast method.

Example of bound service

Started services cannot return results/values or interact with its starting component. Bound services on the other hand can send data to the launching component (client). So for example a bound service might be playing an audio file and sending data regarding audio start/pause/stop and the time elapsed to the launching Activity component so that the UI can be updated accordingly.

So the client component starts the bound Service and binds to it. Multiple components can bind to the service simultaneously. Obviously when the client is done, it can unbind. When the last bound client unbinds, the runtime will terminate the service. Internally, the Service keeps a reference counter holding the number of bound components which on decrementing to 0 triggers the destruction of the Service.

Binding is done using the Context.bindService() method whereas it is terminated with Context.unbindService(). The binding is also destroyed if the client component’s lifecycle ends.

When a client component binds to a bound service it retrieves a communication interface for sending requests and receiving responses within the process or even across processes. It returns an IBinder interface implementation from its onBind() method through the ServiceConnection interface supplied by the binding client component when invoking bindService(). This IBinder is used as the communication channel by the bound component. The component invokes methods of the communication interface and the execution happens in the Service.

Here’s a sample service to which our component will bind to:

public class TestBoundService extends Service {
    private String TAG = "TestBoundService";
    private IBinder myBinder = new MyBinder();

    public TestBoundService() {}

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate called");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind done");
        return myBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return false;
    }

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

    // Methods used by the binding client components

    public void methodOne() {
        // some code
    }

    public void methodTwo() {
        // some code
    }
}

The onBind() method is called when the client component binds to the Service for the first time through bindService(). It returns an IBinder implementation that the client can use to interact with the Service by calling methods in the Service class. This IBinder implementation is basically a communication interface to send requests and receive responses either within a particular process or across processes. On the other hand, onUnbind() is called when all bindings are unbound.

The IBinder is returned to the client through a ServiceConnection interface that the client has to supply while binding. Let’s see how the binding is done from the client component:

Intent intent = new Intent(this, TestBoundService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

So the Intent defines the Service to bind to and shouldn’t contain any other extra data. The binding client has to provide a ServiceConnection implementation which would look like this:

TestBoundService testBoundService;
boolean isBound = false;

private ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        TestBoundService.MyBinder binder = (TestBoundService.MyBinder) service;
        testBoundService = binder.getService();
        isBound = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        testBoundService = null;
        isBound = false;
    }
};

This way the binding client gets notify when the binding is established or disconnected. The last argument (flags) passed to bindService() defines the operation options. These options can either determine the restart strategy on destruction of the Service or the rank of the Service’s process. For instance BIND_AUTO_CREATE is used commonly which recreates the Service if it is destroyed when there’s a bounding client.

Now that you’ve the testBoundService object, you can call important methods like methodOne() and methodTwo() for the sake of communication and receive responses. Note this execution happens on the thread of the calling client. Hence if it’s the UI thread, long running operations should be prevented and a new Thread should be spawned to handle them in background in methodOne() and methodTwo().

Although multiple clients can bind to the service, the system calls your Service’s onBind() only once when the first client binds to retrieve the IBinder. The system then won’t call onBind() again when any additional clients bind. Instead it’ll deliver the same IBinder.

Starting service automatically

Many times we want to start our service automatically, for example at boot time. We know to start a Service we need a component to start it. We could use a Broadcast receiver that starts our service. If for example, we want to start it at the smartphone boot time, we create first a Broadcast receiver that listens to this event and then it starts the service.

public class BootBroadcast extends BroadcastReceiver {
    @Override
    public void onReceive(Context ctx, Intent intent) {
        ctx.startService(new Intent(ctx, MyService.class));
    }
}

and in the Manifest.xml

<receiver android:name=".BootBroadcast">
  <intent-filter
     action android:name="android.intent.action.BOOT_COMPLETED"/>
  </intent-filter>
</receiver>
comments powered by Disqus