S. Gökhan Topçu
gtopcu@gmail.com
You can use services in Android to handle inter-Activity tasks or to do some background work. A service can run independently of the activity or activities which started or are bound to it and has its own lifecycle. They can also be used for inter-process communication, and depending on your design, may also support handling multiple requests simultaneously. Services do not provide a user interface and are usually used to process long-running operations, such as network transfers, IO operations, playing music, etc. You create a service by extending the android.app.Service abstract class, and you need to define it in your manifest just as you would define any other activity, content provider, or broadcast receiver:
<service android:name=".MyService"
android:exported="false"/>
The android:exported="false" attribute is used to keep the service private to your application. Though only the android:name attribute is required, you can also include an intent-filter element as well if you will be exporting your service to other applications and you want your service to respond to any service intents with the specified filters.
There are two types of services:
1. Started services
2. Bound services
This blog will demonstrate the first one: Started Services, which are services that do not allow binding. I will be demonstrating how to use bound services in my next blog. Note that a bound service can also be started, but a started service does not have to allow binding (if you return null from onBind() method). You will understand the mechanics as you move along.
Started Services
A started service is usually used to handle a single task and stops itself when done and do not return a result to its starter. A service is started by an activity using the startService() method. Since a service's lifecyle is independent of the starter/binder activity or activities, a started service does not get destroyed even if the activity that started it is destroyed. So a service needs to be stopped manually when the task is complete by using the method stopService() from the starter activity, or by calling method stopSelf() from within the service.
You should also be aware that services are created and run using the default application process, so if you do not create additional threads, or use AsyncTask / Handler classes, then anything you do in your service will be executed on the main application thread (UI thread). And if your task takes some time to execute (which is probably why you created a service in the first place), then you might get an ANR (Application Not Responding) error, which is typically after 5 minutes and your app be killed by the OS.
When a service is started using the startService() method from an activity, first the service's onCreate() method is called. And then, onStartCommand() will be called by the system with the intent used when calling startService(), as well as the flags and a unique id for this start request which you can use later to stop the service. Last, onDestroy() will be called when the service is stopped. Below is a simple service which will stop itself after waiting for 5 seconds, and an activity which starts it:
MyService.java:
package com.gtware.android.blogs.services;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;
public class MyService extends Service {
private int latestStartId;
@Override
public void onCreate() {
Toast.makeText(this, "Service Created", Toast.LENGTH_SHORT).show();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
latestStartId = startId;
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(5000);
stopSelfResult(latestStartId);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return START_STICKY;
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "Service Done", Toast.LENGTH_SHORT).show();
}
}
MyActivity.java:
package com.gtware.android.blogs.services;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main1);
}
public void startService(View view) {
Intent i = new Intent(this, MyService.class);
startService(i);
}
}
Layout main1.xml contains a simple button which will call the startService() method in the activity. We stop the service using method stopSelfResult(latestStartId) - the new version of stopSelf(int) - which will only stop the service if no other startService() was requested by a caller and thus resulted a call to onStartCommand() (meaning the service was not started by any other callers before it was finished processing the request). If you run this example, you will see the starting/stopping Toast notifications the service outputs during its lifecycle. You can enhance this example to do something useful in your app. A service will only be created (and onCreate() method will be called) when no started instance of the service exists, and since we are not providing binding with this service, we return null from onBind() method. Notice that unlike activities, you do not need to call super() from any of the service's methods.
Just like activities, services can be killed by the Android OS if the system needs more memory. When a service is killed, it's automatically restarted when there're enough resources again. You indicate how your service should to be started when killed with the flag returned from the onStartCommand() method, which in this case is START_STICKY. This flag tells the OS to not to re-send the last intent, and will send a null one if there are no other pending intents so you should first check your intent before executing any tasks.
IntentService
If you need simple tasks queuing and do not require parallel processing of requests, then IntentService is your man. You start by extending the android.app.IntentService class and only need to override onHandleIntent() method instead of overriding onStartCommand(). The default implementation will create a worker thread in onStartCommand() method which queues the start requests and delivers them to the onHandleIntent() method for you to process the requests. Make sure you also call super() from any other methods that you override except onHandleIntent() and onBind() so that the service can do its magic . It even stops the service when all pending requests are done so you never need to call stopSelf() or stopService(), and provides a default implementation of onBind() which returns null so no activity can bind to your service. You also need to provide a default constructor which will send a name for your service to the super constructor. Below is the IntentService version of our previous example:
MyIntentService.java
package com.gtware.android.blogs.services;
import android.app.IntentService;
import android.content.Intent;
import android.widget.Toast;
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, "Service Created", Toast.LENGTH_SHORT).show();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//Call super first to have a worker thread created for you
//and intents delivered to onHandleIntent() in a queued fashion
return super.onStartCommand(intent, flags, startId);
}
@Override
protected void onHandleIntent(Intent intent) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void onDestroy() {
super.onDestroy();
Toast.makeText(this, "Service Done", Toast.LENGTH_SHORT).show();
}
}
If you want proof that the IntentService does actually queue the requests in a separate, just call startService() twice from your starter activity. You will see that the UI does not get blocked since IntentService creates a separate worker thread and the service is destroyed after at least 10 seconds instead of just 5.
Service Priority and Handling Multiple Requests
You can define your own service instead of using the IntentService if you need to service multiple requests simultaneously or if you want to define a custom priority for your service. For executing the service tasks separately from the UI thread with a custom priority, you need to use a Handler implementation with a Looper you derive from a HandlerThread constructed using the priority you define.
If the user will immediately notice that your service is killed (such as for a music player service), you can also define your service as a foreground service by calling startForeground() method in your service with a custom Notification with the Notification.FLAG_ONGOING_EVENT flag, which will indicate the user that the started service is still running in the background and the notification will be kept in the notifications panel as long as your service is alive. You can stop the service from running in the foreground by calling stopForeground() afterwards.
The example below does not process requests simultaneously, but is actually an imitation of the IntentService class, queuing the requests using a Handler. You can instead define new threads in onStartCommand() method if you want multi-threading:
MyQueuedService.java
package com.gtware.android.blogs.services;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.widget.Toast;
public class MyQueuedService extends Service {
private MyHandler myHandler;
private final class MyHandler extends Handler {
public MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
try {
Thread.sleep(5000);
//use the unique startId so you don't stop the
//service while processing other requests
stopSelfResult(msg.arg1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void onCreate() {
Toast.makeText(this, "Service Created", Toast.LENGTH_SHORT).show();
//Create a new HandlerThread with a specified priority
HandlerThread thread = new HandlerThread("MyHandlerThread", Process.THREAD_PRIORITY_DEFAULT);
//Start the handler thread so that our Handler queue will start processing messages
thread.start();
//Run the handler using the new HandlerThread
myHandler = new MyHandler(thread.getLooper());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Message msg = myHandler.obtainMessage();
msg.arg1 = startId;
myHandler.sendMessage(msg);
return START_STICKY;
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "Service Done", Toast.LENGTH_SHORT).show();
}
}
As you can see, stopSelfResult(msg.arg1) method is used here again with the int argument indicating which service instance to stop, so that we don't stop the service while executing other requests since stopSelf() stops the service immediately. Our queue service queues all requests using a Handler class, which executes its handleMessage() method using the looper we pass to it in its constructor. You can again start this service multiple times to see for yourself that indeed our work queue handles each intent one by one, and the service is kept alive until the last intent is processed.
In my next blog, I will be demonstrating how to use bound services.
Further Reading:
Using Services in Android Part 2 - Bound Services
Services
Service
IntentService
AsyncTask
Handler
HandlerThread
Looper
Notification
No comments:
Post a Comment
Please leave your feedback below if you found this blog useful, thanks.