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 UI 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.
AsyncTasks provide a reasonable solution for noncritical background tasks (on behalf of the UI) that are completed in under one or two seconds. Broadcast Receivers 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:
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 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.
There are two points to keep in mind. First, the duration of a bound service is the same as the Android component to which it is bound. Second, the bound service destroys itself as soon as it is unbound from its Android component.
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:
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()
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.
<application ...> <activity...></activity> <service android:name=".MyService></service> </application>
By default, services are declared as public, in that they can be accessed by components outside of the application package in which they reside. In order to make a service private, the android:exported
property must be declared as false
within the <service> element of the manifest file. For example:
<service android:name=".MyService" android:exported="false"> </service>
As previously discussed, services run within the same process as the calling component by default. In order to force a service to run within its own process, add an android:process
property to the <service> element, declaring aname for the process prefixed with a colon (:):
<service android:name=".MyService" android:exported="false" android:process=":myprocess"> </service>
The colon prefix indicates that the new process is private to the local application. If the process name begins with a lower case letter instead of a colon, however, the process will be global and available for use by other components.
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.
Each intent is added to the IntentService
’s queue and handled sequentially. Clients send requests through startService(Intent)
calls; the service is started as needed, handles each Intent
in turn using a worker thread, and stops itself when it runs out of work. This "work queue processor" pattern is commonly used to offload tasks from an application's main thread.
This class performs this operation:
Intent
at time.onStartCommand
.A few limitations of an IntentService
to be aware of:
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.
Android IntentService using BroadcastReceiver
BroadcastReceivers
are used to transfer messages across applications or between a service and an activity. To transfer data between a service and an activity we need to use a LocalBroadcastManager
. LocalBroadcastManager
class comes with the support library and is used to transfer data locally only. You cannot transfer data outside the application.
In the following section, we’ll create an application that passes a string to the IntentService
which returns it to the Activity
. Thanks to IntentService
, this happens sequentially.
Following is a code for MainActivity
.
public class MainActivity extends AppCompatActivity { MyReceiver myReceiver; public static final String ACTION_KEY = "ACTION_KEY"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void btnClick(View v) { Intent intent = new Intent(MainActivity.this, MyIntentService.class); intent.putExtra("data", "boo"); startService(intent); } private void setReceiver() { myReceiver = new MyReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_KEY); LocalBroadcastManager.getInstance(this).registerReceiver(myReceiver, intentFilter); } @Override protected void onStart() { setReceiver(); super.onStart(); } @Override protected void onStop() { unregisterReceiver(myReceiver); super.onStop(); } private class MyReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String message = intent.getStringExtra("broadcastMessage"); Log.d(TAG, "onReceiveResult: " + message); } } }
Following is a code for MyIntentService
.
public class MyIntentService extends IntentService { public MyIntentService() { super(MyIntentService.class.getName()); } @Override protected void onHandleIntent(Intent intent) { String data = intent.getStringExtra("data"); LocalBroadcastManager.getInstance(getApplicationContext()) .sendBroadcast(intent.putExtra("broadcastMessage", "OK")); } }
Android IntentService using ResultReceiver
Android ResultReceiver
is used to receive callback results from someone. It’s different from a BroadcastReceiver
. A BroadcastReceiver
receives all kinds of messages. It can receive messages from outside the activity too.
A ResultReceiver
is specifically used for receiving messages internal to the application. It then can check the result type from the list of result types defined in it. Based on that it can forward the information to the Activity.
The IntentService
can use the send method to pass on the data to the ResultReceiver
. The ResultReceiver
has the onReceiveResult
callback method which gets called whenever a new result is received. Based on the result code in the onReceiveResult
, it performs the action.
In the following section we’ll be creating an application in which we return string from IntentService
and then send the results to the ResultReceiver
where based on the result code (type) we’ll do the necessary action.
Do not forget to set the service in the Manifest file.
Following is a code for MainActivity
.
public class MainActivity extends AppCompatActivity { MyResultReceiver resultReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); resultReceiver = new MyResultReceiver(new Handler()); } public void btnClick(View v) { Intent startIntent = new Intent(MainActivity.this, MyIntentService.class); startIntent.putExtra("receiver", resultReceiver); startIntent.putExtra("data", "boo"); startService(startIntent); } private class MyResultReceiver extends ResultReceiver { public MyResultReceiver(Handler handler) { super(handler); } @Override protected void onReceiveResult(int resultCode, Bundle resultData) { switch (resultCode) { case MyIntentService.RESULT_ERROR: Toast.makeText(getApplicationContext(), "Error in Downloading", Toast.LENGTH_SHORT).show(); break; case MyIntentService.RESULT_SUCCESS: String data = resultData.getString("result"); Log.d(TAG, "onReceiveResult: " + data); break; } super.onReceiveResult(resultCode, resultData); } } }
Following is a code for MyIntentService
.
public class MyIntentService extends IntentService { public static final int RESULT_SUCCESS = 2; public static final int RESULT_ERROR = 3; public MyIntentService() { super(MyIntentService.class.getName()); } @Override protected void onHandleIntent(Intent intent) { String data = intent.getStringExtra("data"); final ResultReceiver receiver = intent.getParcelableExtra("receiver"); Bundle bundle = new Bundle(); try { bundle.putString("result", "OK"); receiver.send(RESULT_SUCCESS, bundle); } catch (Exception e) { receiver.send(RESULT_ERROR, Bundle.EMPTY); e.printStackTrace(); } } }
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 String methodOne() { return "methodOne"; } public String methodTwo() { return "methodTwo"; } }
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.
The onServiceConnected()
method will be called when the client binds successfully to the service. The method is passed as an argument the IBinder
object returned by the onBind()
method of the service. This argument is cast to an object of type TestBoundService
and then the getService()
method of the binder object is called to obtain a reference to the service instance, which, in turn, is assigned to testBoundService
. A Boolean flag is used to indicate that the connection has been successfully established.
The onServiceDisconnected()
method is called when the connection ends and simply sets the Boolean flag to false
.
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
.
Let’s create an activity to bind this service and access this method inside the client.
public class MainActivity extends AppCompatActivity { TestBoundService boundService; private boolean isBind; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((Button) findViewById(R.id.btnStart)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getServiceMessage(); } }); } private void getServiceMessage() { String str1 = boundService.methodOne(); String str2 = boundService.methodTwo(); Toast.makeText(MainActivity.this, str1 + " " + str2, Toast.LENGTH_LONG).show(); } ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { TestBoundService.MyBinder binder = (TestBoundService.MyBinder) service; boundService = binder.getService(); isBind = true; } @Override public void onServiceDisconnected(ComponentName name) { boundService = null; isBind = false; } }; @Override protected void onSart() { super.onStart(); Intent intent = new Intent(this, TestBoundService.class); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); if (isBind) { unbindService(serviceConnection); } } }
So there are two recommended mechanisms for implementing interaction between client components and a bound service:
Binder
class and extend it to provide an interface to the service. An instance of this Binder
object is then returned by the onBind()
method and subsequently used by the client component to directly access methods and data held within the service.Messenger
/Handler
implementation because the remote service is running in a different process and, as such, cannot be reached directly from the client. Specifically, the service creates a Handler
instance that will be called when a message is received from the client. In terms of initialization, it is the job of the Handler
to create a Messenger
object which, in turn, creates an IBinder
object to be returned to the client in the onBind()
method. This IBinder
object is used by the client to create an instance of the Messenger
object and, subsequently, to send messages to the service handler. Each time a message is sent by the client, the handleMessage()
method of the handler is called, passing through the message object.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>
How to check if Service is running
public static boolean isMyServiceRunning(Class<?> serviceClass, Context context) { ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { if (serviceClass.getName().equals(service.service.getClassName())) { return true; } } return false; }
Usage
if (!isMyServiceRunning(LocationService.class, contex)) { Intent i = new Intent(context, LocationService.class); context.startService(i); }
Useful links