Prerequisites: Some knowledge of Android activities. Android smartphone (though this one can run in simulator). You can find the gist for this post here.
For some Android applications, there could be tasks that must be performed over long periods of time, whether sporadically (once each couple of hours, at the end of the day) or continuously. For most of these tasks, an Activity
might not be required to be shown at all times in the smartphone’s screen. As an example, network transmission for downloading songs (as in Spotify, Deezer, and the like) or continuous sensing can be happening in the background while the user is playing a video game, sending an email, or not interacting with the smartphone at all.
For these cases, Android provides a special component known as Service, which resembles the concept of services in desktop or server operating system: a piece of software that is run in the background and that exists independently of a graphical interface. Think of a service as an element that will be interacting with other running software pieces to carry out a task.
Here, I aim at explaining the different ways you can create services in Android. There is a great guide at https://developer.android.com/guide/components/services, which by the way should be your starting point for getting into the best and updated practices for Android programming (whether Java or Kotlin). Remember that mobile devices, from the perspective of both research and technology, are an evolving field so you will notice improvements in the hardware and software platforms of devices. So, here I provide a different perspective of Android services that might help you to grasp concepts quicker.
Types of services
Although at this moment the Android guide specifies three types of services, I would say there are only two types of services:
- “Started services”: That is, services that are explicitly started by other components by calling the startService() method. This type of services will be running until the stopMethod() is called. Such method can be called by the service itself or by the caller. It is noteworthy that the service will have an independent life cycle, and it can keep running even if the caller is not running anymore. Note also, that the caller does not have any object reference for the service, so the caller can’t invoke service’s methods. This means that doing
service.myCoolMethod()
is not possible for started services. - Bound services: These services are created when another component “binds” to them. Roughly speaking, this happens by calling the bindService() method from the caller. Now, by default the bound service will be running as long as there are bound “clients” to it. See that I use the word “client”; bound services feels like implementing a client-server paradigm, as the client (caller) will be performing requests to the service (server). This means that the caller has an actual object reference to the service, so a call like
service.myCoolMethod()
is perfectly valid. Think of this as if you are delegating some heavy processing to the service but at the same time you can obtain the outcome of the processing.
See this figure from the official Android documentation:
On the left there are the started services, and on the right the bound services. All services have a life cycle (just like Activities
and other Android components), but the path for services have slight variations depending on their type.
Now, the crazy/interesting part is that… your service can be of the bound or started class at the same time! This is (in my opinion) something good, as you can personalize your service according to your needs. Do you need a service that runs even if your app is not running (started service) but at the same time, whenever you start the app you want to interact with it and consume its services (bound services)? You can! This combination is the one I use when I need to sense data and process it continuously in the background.
Something to notice is that, by default, a Service
are run on the same thread of the caller, so depending on the complexity of the task, you might want to start a thread to avoid affecting the caller. If the caller is an Activity
you might see that the GUI is unresponsive, and remember the shameful Activity Not Responding
message, you definitely don’t want that.
Services in practice
Let’s try to use some very basic services of each type to get our hands dirty. We will create a project in Android Studio using Java. If you want to copy/paste code seamlessly, name the project HelloWorldServices
with the package com.s0lver
. Let’s specify an empty Activity
(I will use 23 as the minimum API, my phone is rather old).
Started services
Let’s start with the started type of services. As services are another basic element in Android programming (remember: Activity, BroadcastReceiver, Service, Intent, ContentProvider, see https://developer.android.com/guide/components/fundamentals), they must be defined on the Android Manifest. So let’s update our AndroidManifest.xml
including the name of the service we will be creating.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.s0lver">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="com.s0lver.MyStartedService" />
</application>
</manifest>
Now, let’s create the MyStartedService
, by adding a class named MyStartedService
, and we will extend the Service
class. This will force us to implement the onBind()
method, we will return null
here (recall, currently we are exploring the started service type and not the bound service type). Right now, our service looks like this:
package com.s0lver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.Nullable;
public class MyStartedService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
And virtually does nothing. Now let’s make our service do something as soon as it is started. We will simply output a string in the logger periodically. Let’s do this as follows:
package com.s0lver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import java.util.Timer;
import java.util.TimerTask;
public class MyStartedService extends Service {
private Timer myTimer;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(this.getClass().getSimpleName(), "onStartCommand()");
String name = intent.getStringExtra("name");
doTask(name);
return START_STICKY; // START_NOT_STICKY
}
private void doTask(final String name) {
myTimer = new Timer();
myTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Log.i("MyServiceStarted", String.format("Hello %s", name));
}
}, 0, 3 * 1000);
// TODO Use this code rather than previous one to see a Toast shown every 3 seconds.
// you might find handler and runnable weirds, ignore them for now, they are needed if
// you want to show a Toast from a Service...
// final Handler handler = new Handler();
// myTimer.scheduleAtFixedRate(new TimerTask() {
// @Override
// public void run() {
// handler.post(new Runnable() {
// @Override
// public void run() {
// Toast.makeText(MyStartedService.this, String.format("Hello %s", name), Toast.LENGTH_SHORT).show();
// Log.i("Timer", String.format("Hello %s", name));
// }
// });
// }
// }, 0, 3 * 1000);
}
@Override
public void onCreate() {
Log.i(this.getClass().getSimpleName(), "onCreate()");
}
@Override
public void onDestroy() {
Log.i(this.getClass().getSimpleName(), "onDestroyService()");
myTimer.cancel();
super.onDestroy();
}
}
I also included some Logging for the life cycle of this service. See the onStartCommand()
method, it is invoked when the service is started. It will extract a parameter from the intent that just was received and will call a dummy method that will run a TimerTask
each 3 seconds, outputting the name in the Logcat. See the return value for the onStartCommand()
, it refers to how would Android treat the service if it needs to be killed (sticky means the service would be restarted, so you should prepare your code for such restarting). To see the service running, let’s update the code of our empty Activity
as follows:
package com.s0lver;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private Intent serviceIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
serviceIntent = new Intent(this, MyStartedService.class);
serviceIntent.putExtra("name", "Rafael");
startService(serviceIntent);
}
@Override
protected void onDestroy() {
Log.i(this.getClass().getSimpleName(), "onDestroy()");
stopService(serviceIntent);
super.onDestroy();
}
}
See how we call startService()
and stopService()
using an Intent that links the Activity
with the class type of our service. Think of an Intent as the message that travels within Android components and that enables to perform tasks (Intents have types, or identifiers for that matter; here the id/type is our class, but there are other types like Intent.ACTION_CALL
, MediaStore.ACTION_IMAGE_CAPTURE
, etc.).
Now, run the app. You will see in the Logcat that messages are displayed and you will see
04-19 16:01:41.126 14652-14652/com.s0lver I/MyStartedService: onCreate()
04-19 16:01:41.126 14652-14652/com.s0lver I/MyStartedService: onStartCommand()
04-19 16:01:41.128 14652-14694/com.s0lver I/MyServiceStarted: Hello Rafael
04-19 16:01:44.127 14652-14694/com.s0lver I/MyServiceStarted: Hello Rafael
04-19 16:01:47.127 14652-14694/com.s0lver I/MyServiceStarted: Hello Rafael
04-19 16:01:50.128 14652-14694/com.s0lver I/MyServiceStarted: Hello Rafael
04-19 16:01:53.127 14652-14694/com.s0lver I/MyServiceStarted: Hello Rafael
04-19 16:01:56.127.e., top 14652-14694/com.s0lver I/MyServiceStarted: Hello Rafael
If you navigate out of the app (don’t close it, i.e., don’t select back on the Activity
) by pressing your phone home menu and doing something in another app, you will see that messages are still displayed. This means your service is still active regardless of your app life cycle: remember, your service will be active even if the app is not. Navigate back to your app (using the overview button, i.e., the most right button) and now end it using the back button. You will see in the Logcat the onDestroy()
messages for the Activity
and the Service
.
Bound Services
Now let’s have a quick look to the bound services. The Android guide currently states three ways to bind to a service: extending the Binder
class, using a Messenger
, or using Android Interface Definition Language (AIDL). Here we will cover the first way, as it allows to obtain a plain reference to the service and invoke its methods. So let’s add a new class for the bound service as follows:
package com.s0lver;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
public class MyBoundService extends Service {
public IBinder theBinder = new TheBinder();
public class TheBinder extends Binder {
MyBoundService getService() {
return MyBoundService.this;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(this.getClass().getSimpleName(), "onBind()");
return theBinder;
}
public double sum(double a, double b) {
return a + b;
}
public double divide(double a, double b) {
return a / b;
}
@Override
public void onDestroy() {
Log.i(this.getClass().getSimpleName(), "onDestroy()");
super.onDestroy();
}
}
Don’t forget to add MyBoundService
to the AndroidManifest.xml
file. Now, have a look to the TheBinder
class, which extends the Binder class and will be providing the actual reference to our bound service. See the onBind()
service, which is invoked whenever a client binds to the service. Finally see the sample add and divide methods, which help to demonstrate how we can explicitly call public methods of our service.
In order to use the sum and divide methods of our service, let’s update the GUI (activity_main.xml
) of our Activity
as follows (this will create two buttons):
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnSum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickSum"
android:text="Sum"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnDivide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickDivide"
android:text="Divide"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Now, let’s see the changes in MainActivity
, which will be the caller (client) of the bound service:
package com.s0lver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private MyBoundService myBoundService;
private Random random;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
random = new Random(0);
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, MyBoundService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(connection);
super.onDestroy();
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
MyBoundService.TheBinder binder = (MyBoundService.TheBinder) service;
myBoundService = binder.getService();
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
}
};
public void onClickSum(View view) {
double a = random.nextDouble();
double b = random.nextDouble();
double sum = myBoundService.sum(a, b);
Toast.makeText(this, String.format("%s + %s = %s", a, b, sum), Toast.LENGTH_SHORT).show();
}
public void onClickDivide(View view) {
double a = random.nextDouble();
double b = random.nextDouble();
double division = myBoundService.divide(a, b);
Toast.makeText(this, String.format("%s / %s = %s", a, b, division), Toast.LENGTH_SHORT).show();
}
}
Notice the onStart()
method of this Activity
, the call to bind to the service is there. Such call will require a connection ServiceConnection
parameter, which is a field of our Activity
. In this connection object we have two callbacks, one for when we are bound to the service and one for when we unbind. For the first callback, see that we can extract an object reference to our service from the TheBinder
object.
From here, we are free to use a reference to the bound service as if it was a regular Java object. See how the onClickSum()
and onClickDivide()
are consuming this “API” provided by the service.
Finally, see how we request the unbinding from the service on the onDestroy()
method of our Activity
. This means that the service will be alive as long as we don’t finish the Activity
. Make the same exercise as previously, navigate out of the activity (don’t close it) and you won’t see the onDestroy()
message in the Logcat. Go back to the app and now actually close it and you will see such message. The message appears as our activity was the only client bound to the service, and according to the documentation, a bound service is automatically destroyed when all clients unbind from it.
Issues with services
If the tasks to be executed by services are short or with a low frequency, you shouldn’t have any issue or being worried about anything else. However, if your application requires consistent execution of tasks during long periods of time some complications might appear. You must remember that mobile development targets devices with computing (memory) and energy restrictions. Because of this, Android uses some aggressive energy savings methods that can impact on services’ execution. As a consequence, services can be stopped or paused and then relaunched by Android under certain conditions like low memory or idleness. You have two options here:
- You implement your service and elements in such a way they are ready to be paused and then reactivated by Android. You should look into the life cycle of a service to know which methods will capture the re-launching of your service to continue its processing. This is similar to handling the life cycle of an
Activity
, which can be paused and restarted when the user navigates through apps, after receiving a phone call, etc. - Let Android know that your service should be treated with high priority, in such a way it is less likely to be stopped-restarted. Be mindful here, if your service is not performing something vital for your app, you definitively should go with option 1, so Android will make the best of the smartphone’s resources; in other words, don’t go with option 2 just to avoid the work of handling the service’s life cycle: be responsible and aware of smartphone’s constrained resources. You let a service receive this treatment by setting it as a foreground service
One note on the foreground service concept. Android documentation states that there are background, foreground and bound services. Here I just want to clarify that when a service is said to be of background type (yeah, background service is a pleonasm) it means the user is completely unaware of its execution. Instead, in a foreground service the user will know there is something being executed because a notification will be shown in the taskbar.
In fact, the process to mark the service as a foreground service involves putting a notification for the user. As stated, the idea behind this is that the user will know that something is being executed, so Android will not touch such operation. If you have used Spotify, you will see that when you are playing a song, a notification is always shown and you can not swipe it, so Android will never interrupt the song playing.
The following code would mark the StartedService
as a foreground service:
package com.s0lver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
public class MyStartedService extends Service {
private Timer myTimer;
private static final int ID = new Random().nextInt(99999);
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(this.getClass().getSimpleName(), "onStartCommand()");
String name = intent.getStringExtra("name");
doTask(name);
NotificationCompat.Builder b = new NotificationCompat.Builder(this, "channel_id_value");
b.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("MyStartedService")
.setContentText("MyStartedService has been, well, started...");
startForeground(ID, b.build());
return START_STICKY; // START_NOT_STICKY
}
private void doTask(final String name) {
myTimer = new Timer();
myTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Log.i("MyServiceStarted", String.format("Hello %s", name));
}
}, 0, 3 * 1000);
}
@Override
public void onCreate() {
Log.i(this.getClass().getSimpleName(), "onCreate()");
}
@Override
public void onDestroy() {
Log.i(this.getClass().getSimpleName(), "onDestroyService()");
myTimer.cancel();
stopForeground(true);
super.onDestroy();
}
}
Replace the code on MainActivity
with the corresponding version for the StartedService
interaction. You can keep the GUI. Now, when you run the app you will see that the notification is shown and you can’t remove it from the taskbar as the service is in foreground and users must always know that there is something running permanently there.
When you want to stop a foreground service, you should call stopForeground()
. From this moment, Android can treat your service as a regular one.
Using a foreground service in combination with a started + bound service will allow you to have a service running indefinitely, not killed by Android, and with which you can interact through an API that you can freely define. Think of the possibilities you have by having an app running all the time (for research on smartphone-based sensing this is a must, and probably one of the few cases you would want to do something like this), but also think of the impact on resources your app will be producing.
Summary
We have presented the concepts on Android Services here. Android is an evolving platform, so be sure to check the official documentation as more elements are added that make it simpler to develop your apps. The lesson about services is to evaluate your requirements and then select the type/configuration of services you need. Hopefully, by reading this short tutorial now you have the basic knowledge to go and investigate deeper on the Android documentation and develop something exciting!
You can find the gist for this post here.
[I hope soon to document a code for continuous GPS tracking, which was the key concept in my PhD thesis.]