There are quite a few misnomers when it comes to the Android Service class. Let’s clear some of them up.
What most people think of when they think of services
- run independently of user interaction
- don’t commonly have a GUI or can run without one
- use a client for interaction (web browser, db client, sockets)
- independent threading
- long running
This is the common understanding of a UNIX service
How Android defines a service
- long running
- does not provide a user interface
- independent of application lifecycle
- can be “bound” to
You’ll notice that threading it not part of this. This is the source of great confusion for many developers. A service is in the “background” in that it does not interact with the user but it still runs on the main thread. The same thread that handles user interaction, View hierarchy updates and activity lifecycle events. When you start a service it is blocking user interaction.
This is rather inconvenient, so the SDK includes a class that puts the threading on a separate thread.
Introducing IntentService
http://developer.android.com/reference/android/app/IntentService.html
IntentService is a Service that has baked-in asynchronous handling. Every service (IntentService or regular Services) is started by a call to
Context.startService(android.content.Intent)
a regular Service would then handle this Intent starting with onStartCommand
using the main thread.
If you were doing some long running operation (reading from a db, reading from the filesystem, long computation) you would need to create your own threading model, whether it be with an ExecutorService or using a Handler
IntentService does this work for you. It sends intents to a worker thread that calls a method you will be overriding called onHandleIntent(android.content.Intent)
. All code in onHandleIntent
will be done on a single worker thread.
A simple IntentService could look like this:
public class SimpleLoggingIntentService extends IntentService {
/**
* A constructor is the only other method you need to override. It's
* important to call super with a string unique to this class as it will
* be used to name the worker thread.
*/
public SimpleLoggingIntentService() {
super("SimpleLoggingIntentService");
}
/**
* This is called on the worker thread for each intent received through
* startService
*/
@Override
protected void onHandleIntent(Intent intent) {
Log.d("SimpleLoggingIntentService","starting onHandleIntent")
// Do work here (downloads, DB access, CPU intensive operations). We'll
// simulate with a sleep call.
try {
Thread.sleep(5000); //sleep 5s.
} catch(InterruptedException ex) {
Log.e("SimpleLoggingIntentService", ex.getMessage(), ex);
}
Log.d("SimpleLoggingIntentService","finishing onHandleIntent")
}
}
This service passes and Intents sent to startService
to onHandleIntent
on a
worker thread which will handle them in serial according to FIFO (first in first out).
Once all the intents have been handled, the IntentService is stopped.
When to use a Service instead of an IntentService
For most situations, an IntentService will handle all that you need. The threading model works well and is usually enough for most applications. However, there are times when a traditional Service is more appropriate. Let’s look at a multithreading example.
Let’s say we want to download lots of images to the app folder on the device. These images are small, maybe profile images at 64x64, but there are a lot of them. We could handle this in an IntentService. We could give an IntentService an Intent for each icon we want to download and it will execute them in serial. But that could be very slow. If we download them in parallel it will be much faster. Let’s create our own service that does this:
public class MultiThreadedService extends Service {
public static final String _URL = "url";
private ExecutorService executorService;
// counter for number of files we have downloaded
private int downloadsStarted;
// counter for number of files done
private int downloadsFinished;
private Handler handler;
@Override
public void onCreate() {
super.onCreate();
// let's create a thread pool with five threads
executorService = Executors.newFixedThreadPool(5);
downloadsStarted = 0;
downloadsFinished = 0;
handler = new Handler();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// lets say that we are sending the url in the intent
String url = intent.getStringExtra(_URL);
// create a runnable for the ExecutorService
DownloadRunnable task = new DownloadRunnable(getApplicationContext(), url, downloadsStarted++);
// submit it to the ExecutorService, this will be put on the queue and run using a thread
// from the ExecutorService pool
executorService.submit(task);
// tells the OS to restart if we get killed after returning
return START_STICKY;
}
/**
* we don't care about binding right now, returning null is the way to do that
*
* @param intent
* @return
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* to be called by the dl task. if we have exhausted our list of dl's let's shutdown.
*/
private void finished() {
if (downloadsFinished == downloadsStarted) {
handler.post(new Runnable() {
@Override
public void run() {
Log.d("MultiThreadedService", "downloaded " + downloadsFinished + " images, shutting down.");
stopSelf();
}
});
}
}
/**
* dl's the image in question to <index>.jpg
*/
private class DownloadRunnable implements Runnable {
private static final int BUFFER_SIZE = 4096;
private Context context;
private String url_str;
private int index;
public DownloadRunnable(Context context, String url, int index) {
this.context = context;
this.url_str = url;
this.index = index;
}
@Override
public void run() {
URL url = null;
HttpURLConnection httpConn = null;
try {
url = new URL(url_str);
httpConn = (HttpURLConnection) url.openConnection();
// opens input stream from the HTTP connection
InputStream inputStream = httpConn.getInputStream();
String saveFilePath = context.getFilesDir() + File.separator + index + ".jpg";
// opens an output stream to save into file
FileOutputStream outputStream = new FileOutputStream(saveFilePath);
int bytesRead = -1;
byte[] buffer = new byte[BUFFER_SIZE];
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
Log.d("MultiThreadedService", "File downloaded to " + saveFilePath);
} catch (Exception e) {
Log.e("MultiThreadedService", e.getMessage(), e);
} finally {
if (httpConn != null) {
httpConn.disconnect();
}
}
downloadsFinished++;
finished();
}
}
}
This will download every image given to it using the ExecutorService but will return immediately from onStartCommand
. Once all the images are downloaded the service will shut itself down. This runs independently of user interaction and offloads the slow download execution to worker threads. If you want to see all the code go to https://github.com/browep/ServicesTutorial.
Further Reading
There is a lot more to Services and anyone making a decently sized app should know when to use them and when not to. See: http://developer.android.com/guide/components/services.html for further reading.