Android Service with multiple concurrent threads

rokuoku
4 min readFeb 22, 2020

In this post I’m going to explain step-by-step how to create Android background service which runs multiple concurrent threads.

  1. First we need to create service class (File->New->Service->Service). It is better to remove all boilerplate code inside the created class.
  2. Then we need to add the created service in the manifest file (AndroidManifest.xml). Set “android:exported” attribute to “false” if the service doesn’t need to be accessible to other apps.

3. Declare ThreadPoolExecutor executor member variable which we will be sending our tasks to and instantiate it in onCreate().

4. Now in onStartCommand() we can start executing our tasks:

5. But in order for our task to start executing, first we need to start the service itself. For that we will use static method launchService(). In order to make things more interesting, let’s pass some data to service, like some arbitrary string, using an intent.

Also, according to Android documentation “ If your app targets API level 26 or higher, the system imposes restrictions on using or creating background services unless the app itself is in the foreground. If an app needs to create a foreground service, the app should call startForegroundService(). That method creates a background service, but the method signals to the system that the service will promote itself to the foreground. Once the service has been created, the service must call its startForeground() method within five seconds.”

Therefore, we will be creating the service using context.startForegroundService() for API level 26 or higher, and context.startService() for API below 26.

6. We also need to add the following permission in the manifest file:

7. In onCreate() method we will call startForeground() method in order to start the service. However, since our service will be running in foreground, it will need to show notification to user, and we need to pass this notification to startForeground() method. We will create the notification in the onCreate() method as well.

8. Let’s modify our onStartCommand() method to get the data we passed to service in launchService.

9. Now we can launch our service from the main activity like below (the loop is for creating multiple threads):

10. But not so fast. We need to take care of closing all the treads we created and stopping the service itself.

First, let’s do the easiest — shutdown the ThreadPoolExecutor int the onDestroy() method of the service.

11. And now the tricky part — we need to stop our service, but only after all our threads were finished.

Here is quote from Android documentation describing our situation: “ If your service handles multiple requests to onStartCommand() concurrently, you shouldn't stop the service when you're done processing a start request, as you might have received a new start request (stopping at the end of the first request would terminate the second one). To avoid this problem, you can use stopSelf(int) to ensure that your request to stop the service is always based on the most recent start request. That is, when you call stopSelf(int), you pass the ID of the start request (the startId delivered to onStartCommand()) to which your stop request corresponds. Then, if the service receives a new start request before you are able to call stopSelf(int), the ID doesn't match and the service doesn't stop.”

That is, we should call stopSelf(int) in our thread only if all other threads (created before it) were finished. We will implement this using synchronized TreeMap (which is sorted, and therefore more suitable for our purposes then a regular HashMap), which will contain startId of the thread as a key and a boolean value set to true if the thread is still running and false otherwise. When attempting to close the thread, we will first check if all previous threads were finished or not.

First, let’s create the map. In order to create it synchronized we will use Collections.synchronizedSortedMap method.

Note: For better performance ConcurrentHashMap or ReadWriteLock might be used instead of synchronized SortedMap.

In onStartCommand method, when creating a new thread, we will be adding a new element to it. And in the end of the thread we will attempt to close the service in the new method stopSelfIfAllThreadsFinished.

Here is how we implement stopSelfIfAllThreadsFinished: first, we will mark the thread as finished by setting the boolean value mapped to the threads’s startId to false, and then try to stop every service until first not finished thread left. Since stopSelf(int) will close a service only if the startId passed to is the most recent, there is no risk to close the service prematurely.

12. And now we can launch our service:

Note: I was modifying the code while copy-pasting it from my project, so there might be some minor mistakes in it. Please let me know if you notice any.

Check my blog for other articles like this one.

--

--