Android Programming: Simple Concurrency W/O AsyncTask or RxJava

Concurrency is widely regarded as one of the most difficult challenges among all fields of computation. I was first introduced to concurrent programming in high level languages, and the magic did not disappear until I studied it in C/C++.

If you are still wondering how it works under the hood then I suggest that as a learning goal, but we will not be going that low level today. Instead we will look at a practical way to write concurrent code which gives us more control than AsyncTask, but requires orders of magnitude less configuration (barely any at all) than a very expansive library like RxJava. It also requires no 3rd party libraries.

Motivating yourself to learn this topic should be easy: A slow program provides a bad user experience, and if the program is too slow, the Android OS will prompt the user to kill it with fire.

You can still learn the basics of concurrency from this article if you are a Kotlin Android developer, but in Kotlin I just use Coroutines; in case you are wondering.

In this practical article (with code examples), I will explain:

  • What is concurrency
  • How to set up a class which uses one of Java’s standard libraries: Executors (java.util.concurrent.Executors)
  • How to hook your Executor class into the Android OS to access the Android’s main thread (a.k.a. UI thread)
  • How to actually use it to write some concurrent code

I know that concurrent code can be scary and intimidating, but I will do my best to explain it clearly and simply.

Concurrency

Generic stock photo of Train tracks representing concurrent programming

In theory, concurrency is quite simple and familiar to any human being; in fact we are extraordinarily good at it. For example, going for a walk and listening to a podcast at the same time represents the brain processing two activities (probably more but you get the idea) at the same time.

In software development, it usually means that if our application needs to download a big file in order to work (or any kind of long running read, write, computation, or event that may occur in a non-deterministic timeframe), we will almost always want to write concurrent code to solve such problems. Modern devices are designed to be able to run concurrent code, but it is your job as the developer to instruct the device on doing so.

I will not spend much time here, but let us review some basic concepts and terms. A running program on almost any device or operating system (including Android) is called a “process”. Each process has self-contained virtual memory space, and self-contained processing power, which are provided by the operating system using strict access controls. It is important to understand that this word process implies something that is actively processing/computer/executing/running machine instructions which we give to it by writing program source code.

Threads are really almost identical to processes (they compute the code they are given), but they exist within a single process. Also, you can have more than one thread, and because they share the self-contained virtual memory space of their parent process, they can communicate with each other very easily.

Side note: It is possible to perform Inter-Process Communication (IPC) but for obvious reasons, the Android OS manages IPC very carefully; whereas threading in process memory space is much less restrictive.

Do not worry too much if this seems vague; I did my best to explain it simply, but that means leaving out implementation details. As long as you understand the general idea, you will be able to use threads in practice after I show you how.

What Is An Executor?

public interface Executor {
public void execute(Runnable runnable);
}

It is actually just a Java interface with a single abstract method call execute, which accepts a Runnable as an argument.

public interface Runnable {
public void run();
}

If you do not know how interfaces and abstract classes work, then I suggest taking my introductory Java course which explains it without using tons of jargon words.

Anyways, all that you need to understand is that an Executor is in principle a way we can feed executable code into one of the threads in an Android process. To hopefully clear up some confusion, think of it like this:

  • The Executor and its execute(…) method is the delivery person/mechanism
  • The Runnable is the package being delivered, and its run(…) method is how the recipient of the package unpacks and uses the code

If you are still confused, give it time and do not let that stop you from learning concurrency in practice.

Setting Up Your Executors

This is actually very easy to do. Before proceeding, I would like to give thanks to Florina Muntenescu and Jose Alcérreca (and everyone else involved in Android Architecture Blueprints). I was lucky to come across their simple and effective implementation which the code below is based on.

ApplicationExecutors.java:

public class ApplicationExecutors {

private final Executor background;
private final Executor mainThread;

public Executor getBackground() {
return background;
}

public Executor getMainThread() {
return mainThread;
}

public ApplicationExecutors() {
this.background = Executors.newSingleThreadExecutor();
this.mainThread = new MainThreadExecutor();
}

private static class MainThreadExecutor implements Executor {
private Handler mainThreadHandler = new Handler(
Looper.getMainLooper()
);

@Override
public void execute(Runnable command) {
mainThreadHandler.post(command);
}
}
}

There are many ways to create an Executor, but here we just read or write some data to local device storage. Therefore a single thread behind our Executor reference variable instead of a collection of threads like a Thread Pool, will work.

Here, I use a static factory method from Executors.java (not to be confused with Executor.java) to create a new Thread, wrapped by an Executor:

this.background = Executors.newSingleThreadExecutor();

Even most junior developers are aware that every Android process has a special thread that it uses for drawing, monitoring, and updating the user interface (think Activities and Fragments). Since this is something that is specific to the Android OS (i.e. not part of the Java Standard Library), we need to find a way to get a hook to that special thread, which is commonly called the mainThread or uiThread:

private static class MainThreadExecutor implements Executor {
private Handler mainThreadHandler = new Handler(Looper.getMainLooper());

@Override
public void execute(Runnable command) {
mainThreadHandler.post(command);
}
}

For those unfamiliar with Android platform libraries like Handler and Looper, just understand that MainThreadExecutor wraps around the Handler, and the Handler.post(…) is exactly the same thing as Executor.execute(…) but with a different name. It allows us to safely feed executable code into the mainThread. The reason why this is different from just creating a newSingleThreadExecutor(), is that the mainThread comes from the system instead of something we create.

Using Your Executors

After setting up that small class, we can now use it. For the moment ignore Continuation<Day>, and just assume this is any Java method which is about to make a call to some kind of local storage device (File system, Room, Realm, whatever). If you cannot read Lambda expressions, I will show you what it looks like without momentarily:

@Override
public void getDay(Continuation<Day> continuation) {
//before this line, we are on the mainThread by default
exec.getBackground().execute(
() -> {
//In this block, we are now on a background thread
Object data;
try {
data = getDayFromStorage();
} catch (Exception e) {
data = e;
Log.d("STORAGE", Log.getStackTraceString(e));
}

Log.d("CURRENT_THREAD", Long.toString(Thread.currentThread().getId()));

final Object finalData = data;

exec.getMainThread().execute(
() -> {
//now we jump back onto the mainThread
Log.d("CURRENT_THREAD", Long.toString(Thread.currentThread().getId()));

if (finalData instanceof Day) continuation.onSuccess(
(Day) finalData
);

else continuation.onException(
(Exception) finalData
);
}
);
}
);
}

Here is what is happening in a ultra professional and high budget info-graphic:

About the lambdas: We know that when we call Exector.execute(Runnable runnable), that we better give it a Runnable. A lambda, in this case, is just a way of writing an anonymous class without using old school carpal tunnel syndrome inducing (to borrow a phrase from Douglas C. Schmidt) Java syntax:

exec.getBackground().execute(new Runnable() {
@Override
public void run() {
//this is where we do background stuff
Object data;
try {
data = getDayFromStorage();
} catch (Exception e) {
data = e;
Log.d("STORAGE", Log.getStackTraceString(e));
}
//...
exec.getMainThread().execute(
new Runnable() {
@Override
public void run() {
//...
}
}
);
}
});

For roughly the third time, I remind anyone confused that a Runnable just represents some chunk of code that we want to execute, which in this case is the body of our lambda/anonymous class’ run method.

Just a convenient way to model successful and failed operations as an interface…:

public interface Continuation<T> {
public void onSuccess(T result);

public void onException(Exception e);
}

…which is supplied by whichever class is calling our storage class:

/Decision maker (logic) class for the Front End
public class DayViewLogic extends BaseViewLogic<DayViewEvent> {

private final IDayViewContract.View view;
private final IDayViewContract.ViewModel vm;

//backend IO devices
private final IDayStorage dayStorage;
private final ITaskStorage taskStorage;

public DayViewLogic(IDayViewContract.View view, IDayViewContract.ViewModel vm, IDayStorage dayStorage, ITaskStorage taskStorage) {
this.view = view;
this.vm = vm;
this.dayStorage = dayStorage;
this.taskStorage = taskStorage;
}

//...
private void onStart() {
dayStorage.getDay(new Continuation<Day>() {
@Override
public void onSuccess(Day result) {
getTasks(result);
}

@Override
public void onException(Exception e) {
view.showMessage(Messages.GENERIC_ERROR_MESSAGE);
view.restartFeature();
}
});
}

//...
}

We cannot use a lambda expression here because lambdas in Java (at least the version I am using may only have one method, like Runnable.run(…) or Executor.execute(…).

Happy coding!

Social Media | Support

This article was written by Ryan Michael Kay. I am a self-taught programmer/engineer who creates educational content for on a wide variety of topics, on a wide variety of platforms. The best way to support me is to follow me on various platforms and join in with my developer community (we have hundreds of members!):

Announcements:

https://www.instagram.com/rkay301/
https://www.facebook.com/wiseassblog
https://twitter.com/wiseAss301

Tutorials & Courses:

Free Tutorials, Live Q&A, Live Coding:
https://www.youtube.com/channel/UCSwuCetC3YlO1Y7bqVW5GHg

Java Desktop Programming w/ JavaFX (Intermediate) — https://skl.sh/31pzCa1

Complete Beginner Introduction To Java Programming (Beginner — Intermediate) — https://skl.sh/3fZbjos

Android Apps With Kotlin & Android Studio (Beginner) — https://skl.sh/2ZU6ZT9

Material Design Android Programming w/ Kotlin (Intermediate) — https://skl.sh/2OrwrYZ

Connect:

LinkedIn-https://www.linkedin.com/in/ryan-kay-808388114/

Self-taught software developer & student of computer science.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store