Android: How to create a background service

It is often handy to keep a service running in the background to manage certain tasks. They can be started from an Activity or a broadcast receiver. Managing the service lifecycle can be tricky unless you follow some key points, as I learned the hard way when creating Air Waves.

First off, services can either be;

  • a) Bound by the activity lifecycle (created and destroyed along with activity). Normally services will be destroyed once they have no more work to do and no remaining activities are binded to them.
  • b) Running along side with activity and only stops when it's done (or told to)

I'll show you how to start and manage the latter, a service that's running independently from an any binded Activities.

Defining

First you have to add some definition about the service in the AndroidManifest.xml file. Under application, simply add:

<service android:name=".ContentCheckService"></service>

That's it for the manifest!

Servicing

The service itself is a rather simple class.

public class AirWavesService extends Service {
// LocalBinder, mBinder and onBind() allow other Activities to bind to this service.
public class LocalBinder extends Binder {
public AirWavesService getService() {
return AirWavesService.this;
}
}

private final LocalBinder mBinder = new LocalBinder();

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


// Variables
protected Handler handler;
protected Toast mToast;


@Override
public void onCreate() {
super.onCreate();

Log.i("Service", "onCreate");

// Initialise UI elements
handler = new Handler();
mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);

// ...
}


@Override
public void onDestroy() {
Log.w("Service", "onDestroy");
// ...

// Clean up UI
mToast = null;

super.onDestroy();
}


@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Service", "onStartCommand");
return android.app.Service.START_STICKY;
}


/**
* Example function.
* @throws AirWavesException
*/
public void doSomethingOnService() throws AirWavesException {
if (!isWiFiEnabled()) {
throw new AirWavesException("No WiFi connection available.");
}

handler.post(new Runnable() {
@Override
public void run() {
mToast.setText("do something");
mToast.show();
}
});
Log.i("Service", "doSomethingOnService() called");
}
}

The value Service.START_STICKY returned by onStartCommand() controls the Service lifecycle. Basically it means that the service will be resurrected if it's killed by Android.

The documentation will explain this far better than I can.

Key functions

Shown above is the important parts of it. The main points you'll need are:

  • onCreate() and onDestroy(): Use this to initialise and clean up your variables, much like in Activity.
  • onStartCommand(): If you were writing a service which is bound to the activity lifecycle, then this is the fun one where you write your service logic. Since ours isn't, then we can write our functions anywhere, like in doSomethingOnService().

 

Note: if you're starting the service from a broadcast receiver then onStartCommand() will be called often, whereas onCreate() won't. You'll need to keep track of this yourself.

Starting the service

Now there's no point having a fancy service if you can't use it. To start it up, you'll need some simple yet (hopefully) familiar code:

Intent i = new Intent(context, ContentCheckService.class);
context.startService(i);

This is the key point you have to remember. If a service is started by startService(), then it will stay alive until you manually terminate it.

Connecting to the service

Once it's up and running, you can connect to the service and use it like a normal object.

The term you're looking for here is "binding" to a service. Services often provide functions which are needed by an Activity, and in order to access those functions easily you need to provide some "binding glue" between the two Activity and Service classes.

vJ17R
These two have been binded.

Within the Service class, notice the definition for LocalBinder. That allows your Activity to connect to the service via a ServiceConnection.

Here's a "trimmed down" version of the Activity class (I'm not kidding!)

public class MainActivity extends Activity {
protected AirWavesService service;
protected AirWavesServiceConnection serviceConnection;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Log.i("Activity", "onCreate");
// ...
}

@Override
protected void onResume() {
super.onResume();
Log.i("Activity", "onResume");
connectToService();
}

@Override
protected void onPause() {
super.onPause();

if (serviceConnection != null) {
unbindService(serviceConnection);
serviceConnection = null;
}
}


// Helper function for connecting to AirWavesService.
private void connectToService() {
// Calling startService() first prevents it from being killed on unbind()
startService(new Intent(this, AirWavesService.class));

// Now connect to it
serviceConnection = new AirWavesServiceConnection();

boolean result = bindService(
new Intent(this, AirWavesService.class),
serviceConnection,
BIND_AUTO_CREATE
);

if (!result) {
throw new RuntimeException("Unable to bind with service.");
}
}


protected class AirWavesServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName className, IBinder binder) {
Log.i("Activity", "onServiceConnected AirWavesService");
service = ((AirWavesService.LocalBinder) binder).getService();
}

@Override
public void onServiceDisconnected(ComponentName className) {
Log.e("Activity", "onServiceDisconnected AirWavesService");
service = null;
}
}


protected void callServiceFunction() throws AirWavesException {
service.doSomethingOnService();
}


@Override
protected void onDestroy() {
Log.e("Activity", "onDestroy");

// If we no longer need it, kill the service
if (!G.isListening && !G.isSpeaking) {
stopService(new Intent(this, AirWavesService.class));
}

super.onDestroy();
}
}

Taking a closer look

484047_10151308073271923_1041064310_n

Alright working our way from top to bottom, you'll see "service" and "serviceConnection" declared at the top. They're important as they bind your Activity to the Service.

Nothing interesting is happening at onCreate().

However, just below is onResume() and onPause() which control the life of the binding connection.

Whenever your Activity is destroyed, the bind is no longer valid and needs to be undone. Rule of thumb is if the number of bindService() and unbindService() calls don't match up, you'll get debug error logs:

Activity has leaked ServiceConnection <X> that was originally bound here. android.app.ServiceConnectionLeaked

With connectToService(), the startService() call will kick-start it into a persistent service. By the time we call bindService(), we can be sure that the service is already up and running, ready for connection.

AirWavesServiceConnection is simply an implementation of ServiceConnection so we can get a handle to the service. In onServiceConnected(), we save the reference to the service and during onServiceDisconnected() we delete that reference.

This isn't really necessary, but callServiceFunction() shows you how to call functions from the service. Pretty easy eh?

And of course, onDestroy() stops the service using stopService() when we no longer need it.

And there you have it, a long life service on Android!

Just wait until you start reading into stuff like foreground services and wake-locks in order!

Sources

A mix of information from my previous post "Android: Detect when internet connectivity is connected" and documentation from the Cling library.

Big thanks to Mark Murphy the CommonsWare guy for hanging around StackOverflow and sharing so much of his knowledge.

 
Copyright © Twig's Tech Tips
Theme by BloggerThemes & TopWPThemes Sponsored by iBlogtoBlog