In this tutorial we’ll explore the Service
component and its superclass, the IntentService
. You'll learn when and how to use this component to create great concurrency solutions for long-running background operations. We’ll also take quick look at IPC (Inter Process Communication), to learn how to communicate with services running on different processes.
To follow this tutorial you'll need some understanding of concurrency on Android. If you don’t know much about it, you might want to read some of our other articles about the topic first.
-
Android SDKAndroid From Scratch: Background Operations
-
AndroidUnderstanding AsyncTask Values in 60 Seconds
-
Android SDKUnderstanding Concurrency on Android Using HaMeR
-
Android SDKPractical Concurrency on Android With HaMeR
1. The Service Component
The Service
component is a very important part of Android's concurrency framework. It fulfills the need to perform a long-running operation within an application, or it supplies some functionality for other applications. In this tutorial we’ll concentrate exclusively on Service
’s long-running task capability, and how to use this power to improve concurrency.
What is a Service?
A Service
is a simple component that's instantiated by the system to do some long-running work that doesn't necessarily depend on user interaction. It can be independent from the activity life cycle and can also run on a complete different process.
Before diving into a discussion of what a Service
represents, it's important to stress that even though services are commonly used for long-running background operations and to execute tasks on different processes, a Service
doesn't represent a Thread
or a process. It will only run in a background thread or on a different process if it's explicitly asked to do so.
A Service
has two main features:
- A facility for the application to tell the system about something it wants to be doing in the background.
- A facility for an application to expose some of its functionality to other applications.
Services and Threads
There is a lot of confusion about services and threads. When a Service
is declared, it doesn't contain a Thread
. As a matter of fact, by default it runs directly on the main thread and any work done on it may potentially freeze an application. (Unless it's a IntentService
, a Service
subclass that already comes with a worker thread configured.)
So, how do services offer a concurrency solution? Well, a Service
doesn't contain a thread by default, but it can be easily configured to work with its own thread or with a pool of threads. We'll see more about that below.
Disregarding the lack of a built-in thread, a Service
is an excellent solution for concurrency problems in certain situations. The main reasons to choose a Service
over other concurrency solutions like AsyncTask
or the HaMeR framework are:
- A
Service
can be independent of activity life cycles. - A
Service
is appropriate for running long operations. - Services don't depend on user interaction.
- When running on different processes, Android can try to keep services alive even when the system is short on resources.
- A
Service
can be restarted to resume its work.
Service Types
There are two types of Service
, started and bound.
A started service is launched via Context.startService()
. Generally it performs only one operation and it will run indefinitely until the operation ends, then it shuts itself down. Typically, it doesn't return any result to the user interface.
The bound service is launched via Context.bindService()
, and it allows a two-way communication between client and Service
. It can also connect with multiple clients. It destroys itself when there isn't any client connected to it.
To choose between those two types, the Service
must implement some callbacks: onStartCommand()
to run as a started service, and onBind()
to run as a bound service. A Service
may choose to implement only one of those types, but it can also adopt both at the same time without any problems.
2. Service Implementation
To use a service, extend the Service
class and override its callback methods, according to the type of Service
. As mentioned before, for started services the onStartCommand()
method must be implemented and for bound services, the onBind()
method. Actually, the onBind()
method must be declared for either service type, but it can return null for started services.
public class CustomService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { // Execute your operations // Service wont be terminated automatically return Service.START_NOT_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { // Creates a connection with a client // using a interface implemented on IBinder return null; } }
onStartCommand()
: launched byContext.startService()
. This is usually called from an activity. Once called, the service may run indefinitely and it's up to you to stop it, either callingstopSelf()
orstopService()
.onBind()
: called when a component wants to connect to the service. Called on the system byContext.bindService()
. It returns anIBinder
that provides an interface to communicate with the client.
The service's life cycle is also important to take into consideration. The onCreate()
and onDestroy()
methods should be implemented to initialize and shut down any resources or operations of the service.
Declaring a Service on Manifest
The Service
component must be declared on the manifest with the <service>
element. In this declaration it's also possible, but not obligatory, to set a different process for the Service
to run in.
<manifest ... > ... <application ... > <service android:name=".ExampleService" android:process=":my_process"/> ... </application> </manifest>
2.2. Working with Started Services
To initiate a started service you must call Context.startService()
method. The Intent
must be created with the Context
and the Service
class. Any relevant information or data should also be passed in this Intent
.
Intent serviceIntent = new Intent(this, CustomService.class); // Pass data to be processed on the Service Bundle data = new Bundle(); data.putInt("OperationType", 99); data.putString("DownloadURL", "http://ift.tt/2ebEbhI"); serviceIntent.putExtras(data); // Starting the Service startService(serviceIntent);
In your Service
class, the method that you should be concerned about is the onStartCommand()
. It's on this method that you should call any operation that you want to execute on the started service. You'll process the Intent
to capture information sent by the client. The startId
represents an unique ID, automatically created for this specific request and the flags
can also contain extra information about it.
@Override public int onStartCommand(Intent intent, int flags, int startId) { Bundle data = intent.getExtras(); if (data != null) { int operation = data.getInt(KEY_OPERATION); // Check what operation to perform and send a msg if ( operation == OP_DOWNLOAD){ // make a download } } return START_STICKY; }
The onStartCommand()
returns a constant int
that controls the behavior:
Service.START_STICKY
: Service is restarted if it gets terminated.Service.START_NOT_STICKY
: Service is not restarted.Service.START_REDELIVER_INTENT
: The service is restarted after a crash and the intents then processing will be redelivered.
As mentioned before, a started service needs to be stopped, otherwise it will run indefinitely. This can be done either by the Service
calling stopSelf()
on itself or by a client calling stopService()
on it.
void someOperation() { // do some long-running operation // and stop the service when it is done stopSelf(); }
Binding to Services
Components can create connections with services, establishing a two-way communication with them. The client must call Context.bindService()
, passing an Intent
, a ServiceConnection
interface and a flag
as parameters. A Service
can be bound to multiple clients and it will be destroyed once it has no clients connected to it.
void bindWithService() { Intent intent = new Intent(this, PlayerService.class); // bind with Service bindService(intent, mConnection, Context.BIND_AUTO_CREATE); }
It's possible to send Message
objects to services. To do it you'll need to create a Messenger
on the client side in a ServiceConnection.onServiceConnected
interface implementation and use it to send Message
objects to the Service
.
private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { // use the IBinder received to create a Messenger mServiceMessenger = new Messenger(service); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; mServiceMessenger = null; } };
It's also possible to pass a response Messenger
to the Service
for the client to receive messages. Watch out though, because the client may not no longer be around to receive the service's message. You could also use BroadcastReceiver
or any other broadcast solution.
private Handler mResponseHandler = new Handler() { @Override public void handleMessage(Message msg) { // handle response from Service } }; Message msgReply = Message.obtain(); msgReply.replyTo = new Messenger(mResponseHandler); try { mServiceMessenger.send(msgReply); } catch (RemoteException e) { e.printStackTrace(); }
It's important to unbind from the Service when the client is being destroyed.
@Override protected void onDestroy() { super.onDestroy(); // disconnect from service if (mBound) { unbindService(mConnection); mBound = false; } }
On the Service
side, you must implement the Service.onBind()
method, providing an IBinder
provided from a Messenger
. This will relay a response Handler
to handle the Message
objects received from client.
IncomingHandler(PlayerService playerService) { mPlayerService = new WeakReference<>(playerService); } @Override public void handleMessage(Message msg) { // handle messages } } public IBinder onBind(Intent intent) { // pass a Binder using the Messenger created return mMessenger.getBinder(); } final Messenger mMessenger = new Messenger(new IncomingHandler(this));
3 Concurrency Using Services
Finally, it's time to talk about how to solve concurrency problems using services. As mentioned before, a standard Service
doesn't contain any extra threads and it will run on the main Thread
by default. To overcome this problem you must add an worker Thread
, a pool of threads or execute the Service
on a different process. You could also use a subclass of Service
called IntentService
that already contains a Thread
.
Making a Service Run on a Worker Thread
To make the Service
execute on a background Thread
you could just create an extra Thread
and run the job there. However Android offers us a better solution. One way to take the best advantage of the system is to implement the HaMeR framework inside the Service
, for example by looping a Thread
with a message queue that can process messages indefinitely.
It's important to understand that this implementation will process tasks sequentially. If you need to receive and process multiple tasks at the same time, you should use a pool of threads. Using thread pools is out of the scope of this tutorial and we won't talk about it today.
To use HaMeR you must provide the Service
with a Looper
, a Handler
and a HandlerThread
.
private Looper mServiceLooper; private ServiceHandler mServiceHandler; // Handler to receive messages from client private final class ServiceHandler extends Handler { ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); // handle messages // stopping Service using startId stopSelf( msg.arg1 ); } } @Override public void onCreate() { HandlerThread thread = new HandlerThread("ServiceThread", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); }
If the HaMeR framework is unfamiliar to you, read our tutorials on HaMer for Android concurrency.
-
Android SDKUnderstanding Concurrency on Android Using HaMeR
-
Android SDKPractical Concurrency on Android With HaMeR
The IntentService
If there is no need for the Service
to be kept alive for a long time, you could use IntentService
, a Service
subclass that's ready to run tasks on background threads. Internally, IntentService
is a Service
with a very similar implementation to the one proposed above.
To use this class, all you have to do is extend it and implement the onHandleIntent()
, a hook method that will be called every time a client calls startService()
on this Service
. It's important to keep in mind that the IntentService
will stop as soon as its job is completed.
public class MyIntentService extends IntentService { public MyIntentService() { super("MyIntentService"); } @Override protected void onHandleIntent(Intent intent) { // handle Intents send by startService } }
IPC (Inter Process Communication)
A Service
can run on a completely different Process
, independently from all tasks that are happening on the main process. A process has its own memory allocation, thread group, and processing priorities. This approach can be really useful when you need to work independently from the main process.
Communication between different processes is called IPC (Inter Process Communication). In a Service
there are two main ways to do IPC: using a Messenger
or implementing an AIDL
interface.
We've learned how to send and receive messages between services. All that you have to do is use create a Messenger
using the IBinder
instance received during the connection process and use it to send a reply Messenger
back to the Service
.
private Handler mResponseHandler = new Handler() { @Override public void handleMessage(Message msg) { // handle response from Service } }; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { // use the IBinder received to create a Messenger mServiceMessenger = new Messenger(service); Message msgReply = Message.obtain(); msgReply.replyTo = new Messenger(mResponseHandler); try { mServiceMessenger.send(msgReply); } catch (RemoteException e) { e.printStackTrace(); } }
The AIDL
interface is a very powerful solution that allows direct calls on Service
methods running on different processes and it's appropriate to use when your Service
is really complex. However, AIDL
is complicated to implement and it's rarely used, so its use won't be discussed in this tutorial.
4. Conclusion
Services can be simple or complex. It depends on the needs of your application. I tried to cover as much ground as possible on this tutorial, however I've focused just on using services for concurrency purposes and there are more possibilities for this component. I you want to study more, take a look at the documentation and Android guides.
See you soon!
No comments:
Post a Comment