Background Tasks in Android: 102

ThreadPool, ThreadPoolExecutor Service

A Threadpool is a collection of threads that can execute multiple instances of a task in parallel.

Because tasks execute in parallel, you may want to ensure that your code is thread-safe. A Threadpool solves three problems

1- Improved performance when executing a large number of asynchronous tasks due to a reduction per-task overhead.

2- A means of bounding and managing resources when executing a collection of tasks

3- It provides developers with various types of queues for storing the tasks. It also provides various mechanisms for handling the scenario in which a task is rejected by the queue when it is full.(This is a benefit of executor)

Threads by default do only three things: they start, do some work and terminate. To keep the thread alive it requires to pretend that thread is doing some work, it is done by running a message loop. Android achieves this by using a looper. The same approach is done for the main thread in android the UI thread.

Furthermore, when dealing with a collection of threads, it adds a new layer of complexity. Since we don't have to bound and manage resources and threads, developing something which can handle all these may not be that easy as you may think. It also increases the possibility of running into bugs associated with Multithreading uses cases

ThreadpoolExecutor provides all of these and allows us to easily manage and assign tasks to a pool of threads. It even takes care of terminating the threads for us appropriately.

Let us understand with an example -

Ever been to a Food joint to get your favourite food order. There are various counters to order and pay for your order. If any of the counters is free you order and pay. But if all the counters are busy you stand in the queue and wait for your turn.

1- For you and all those people who want to order food. Ordering is a task(Runnable, Callable).

2- The counters are the worker thread. Let’s say a Thread Pool

3- The people who are waiting to order are standing in a queue.

In Simple words, Executor executes the task. The executor picks the thread from the Threadpool to execute the task. If no thread is available at the moment then the executor places the task in a queue. If the queue is full, then the task is rejected by the queue.

We will build a Sample Application which will post “Quotes” to the main activity. We will use a Threadpool of minimum 3 threads and multiple tasks will be sent to Threadpool to fetch the quotes and display them. While implementing we will discuss each method and other details of the ThreadPoolExecutor. Let’s begin.

First, let’s build a method to fetch quotes. I’m using a free API to fetch the quotes using URLConnection: keeping it simple. if you want to you can use any other library. And then using JSONObject class to get the quote.

public class QuoteFetcher {

private static QuoteFetcher instance = null;

private String link = "https://api.kanye.rest/";
private static final String TAG = "QuoteFetcher";

private QuoteFetcher() {

}

public synchronized static QuoteFetcher getInstance() {
if (instance == null) {
instance = new QuoteFetcher();
}
return instance;
}

/**
* Method to make json object request where json
* */
public String makeRequest() throws IOException {
String quote = "";
URL url = new URL(link);
HttpURLConnection con = (HttpURLConnection)url.openConnection();
con.setRequestMethod("GET");
con.setDoOutput(true);
con.connect();

BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
String jsonString = sb.toString();
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(jsonString);
quote = jsonObject.getString("quote");
} catch (JSONException e) {
e.printStackTrace();
}

return quote;
}

}

I have made a Singleton class for quoteFetcher as we don't want multiple instances of this being created by different threads.

I will not explain this method in detail as the scope of this article is ThreadPoolExecutor. Still, if any of you face issues in understanding the code I will try my best to answer, just leave your query in the comment section.

Now let’s build our CustomThreadPoolManager

public class CustomThreadPoolManager {

private static CustomThreadPoolManager sInstance = null;
private static int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
private static final int KEEP_ALIVE_TIME = 1;
private static final TimeUnit KEEP_ALIVE_TIME_UNIT;

private final ExecutorService mExecutorService;
private final BlockingQueue<Runnable> mTaskQueue;
private List<Future> mRunningTaskList;

private WeakReference<UiThreadCallback> uiThreadCallbackWeakReference;

static {
KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
sInstance = new CustomThreadPoolManager();
}

// Made constructor private to avoid the class being initiated from outside
private CustomThreadPoolManager() {
// initialize a queue for the thread pool. New tasks will be added to this queue
mTaskQueue = new LinkedBlockingQueue<Runnable>();
mRunningTaskList = new ArrayList<>();
mExecutorService = new ThreadPoolExecutor(NUMBER_OF_CORES,
NUMBER_OF_CORES*2,
KEEP_ALIVE_TIME,
KEEP_ALIVE_TIME_UNIT,
mTaskQueue,
new BackgroundThreadFactory());
}

public static CustomThreadPoolManager getsInstance() {
return sInstance;
}
/* A ThreadFactory implementation which create new threads for the thread pool.
The threads created is set to background priority, so it does not compete with the UI thread.
*/
private static class BackgroundThreadFactory implements ThreadFactory {
private static int sTag = 1;

@Override
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable);
thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND);
// A exception handler is created to log the exception from threads
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
Log.e(Util.LOG_TAG, thread.getName() + " encountered an error: " + ex.getMessage());
}
});
return thread;
}
}

Again we will make this class a singleton, the constructor for this class takes in three parameters.

mTaskQueue-> this contains the tasks that will be sent to process. mRunningTaskList->this hold the currently running task.

mExecutorService- this is the instance of our ThreadPoolExecutor and takes in six parameters.

Minimum_no_thread->The minimum number of threads to keep in the pool. Maximum_no_thread-> The maximum number of threads to keep in the pool. Keep_Alive_time-> If the current number of threads is greater than the minimum threads, then wait time to terminate the extra thread.

Keep_alive_time_unit->Unit of the time

mTaskQueue->The queue used to hold the tasks

BackgroundThreadFactory() -> This holds the logic if any thread rejects the task also by extending ThreadFactory we can implement our own custom thread factory which provides us with the customization like set Thread_NAME, set Thread_Priority etc.

// Add a callable to the queue, which will be executed by the next available thread in the pool
public void addCallable(Callable callable){
Future future = mExecutorService.submit(callable);
mRunningTaskList.add(future);
}

This is just adding the callable object in the pending task list mRunningList.

To stop the task in the task queue from running, we just need to clear the task queue. To allow the running threads to be stopped store all future objects in a list and call cancel on every object which is not done.

/* Remove all tasks in the queue and stop all running threads
* Notify UI thread about the cancellation
*/
public void cancelAllTasks() {
synchronized (this) {
mTaskQueue.clear();
for (Future task : mRunningTaskList) {
if (!task.isDone()) {
task.cancel(true);
}
}
mRunningTaskList.clear();
}
sendMessageToUiThread(Util.createMessage(Util.MESSAGE_ID, "All tasks in the thread pool are cancelled"));
}

We will also keep a weak reference to the UI thread, so we can send messages to the UI thread and pass the message to the UI thread with the below methods

public void setUiThreadCallback(UiThreadCallback uiThreadCallback) {
this.uiThreadCallbackWeakReference = new WeakReference<UiThreadCallback>(uiThreadCallback);
}

public void sendMessageToUiThread(Message message){
if(uiThreadCallbackWeakReference != null && uiThreadCallbackWeakReference.get() != null) {
uiThreadCallbackWeakReference.get().publishToUiThread(message);
}
}

With this, our CustomThreadPoolManager is complete now let's create the Custom Callable. Callables are similar to Runnable. I have already explained it in my previous article. If you want to check out the differences between callable vs runnable you can check out it here.

public class CustomCallable implements Callable {

// Keep a weak reference to the CustomThreadPoolManager singleton object, so we can send a
// message. Use of weak reference is not a must here because CustomThreadPoolManager lives
// across the whole application lifecycle
private WeakReference<CustomThreadPoolManager> mCustomThreadPoolManagerWeakReference;
private QuoteFetcher quoteFetcher = QuoteFetcher.getInstance();

@Override
public Object call() {

String quote = "";

try {
// check if thread is interrupted before lengthy operation
if (Thread.interrupted()) throw new InterruptedException();

try {
quote = quoteFetcher.makeRequest();
} catch (IOException e) {
e.printStackTrace();
}// After work is finished, send a message to CustomThreadPoolManager
Message message = Util.createMessage(Util.MESSAGE_ID,
Thread.currentThread().getName() + quote);

if(mCustomThreadPoolManagerWeakReference != null
&& mCustomThreadPoolManagerWeakReference.get() != null) {

mCustomThreadPoolManagerWeakReference.get().sendMessageToUiThread(message);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}

public void setCustomThreadPoolManager(CustomThreadPoolManager customThreadPoolManager) {
this.mCustomThreadPoolManagerWeakReference = new WeakReference<CustomThreadPoolManager>(customThreadPoolManager);
}
}

With everything set up now let's finish our up Main Activity. First lets set up our custom handler to help us in communicating with the UI thread. In its constructor, we pass the looper and the text view in which we need to show our data. The handleMessage method runs on UI thread and updates the message received to the UI element.

// UI handler class, declared as static so it doesn't have implicit
// reference to activity context. This helps to avoid memory leak.
private static class UiHandler extends Handler {
private WeakReference<TextView> mWeakRefDisplay;

public UiHandler(Looper looper, TextView display) {
super(looper);
this.mWeakRefDisplay = new WeakReference<TextView>(display);
}

// This method will run on UI thread
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
// Our communication protocol for passing a string to the UI thread
case Util.MESSAGE_ID:
Bundle bundle = msg.getData();
String messsageText = bundle.getString(Util.MESSAGE_BODY, Util.EMPTY_MESSAGE);
if(mWeakRefDisplay != null && mWeakRefDisplay.get() != null)
mWeakRefDisplay.get().append(Util.getReadableTime() + " " + messsageText + "\n");
break;
default:
break;
}
}
}

With this being in place, there is not much work in the MainActivity.

The complete code for MainActivity can be found at the repo. Please go ahead and check it out.

Here is the demo of the application.

Here is the link for my other story done on binding services. It will help you understand how threading and services can be put together to create a robust background processing system

Next story in this series.

I have written a story for binding services as well, below is the link for the story, it will helpful for anyone who wants to understand the binding services.

A Software Engineer and gamer at heart.