Asynchronous Apis Made easy (Kotlin code)
Today we will be covering a small subset of the dozens of ways one can perform asynchronous processing in Android, along with some Apis provided by Java, and known third party libraries.
AsyncTask
class AsyncTaskExample : AsyncTask<String, String, String>() {
companion object {
private val TAG = AsyncTaskExample::class.java.simpleName
}
override fun onPreExecute() {
super.onPreExecute()
// should print main - this runs on the main thread.
// Gets called before the doInBackground method does
Log.d(TAG, "onPreExecute: " + Thread.currentThread().name)
}
override fun doInBackground(vararg strings: String): String {
// should print 'AsyncTask #{number}' - this runs in a background thread
Log.d(TAG, "doInBackground: " + Thread.currentThread().name)
return strings[0] /* Return anything for this example, such as test1*/
}
override fun onPostExecute(s: String) {
super.onPostExecute(s)
// s should be equal to whatever is returned in the doInBackground method,
// in this case test1
Log.d(TAG, "onPostExecute() called with: s = [$s]")
// Should print main - this runs on the main thread.
// Gets called after the doInBackground method does
Log.d(TAG, "onPostExecute: " + Thread.currentThread().name)
}
}
To execute the AsyncTask, the following is done:
AsyncTaskExample().execute("test1", "test2", "test3")
A list of strings is passed in as required by the type.
The execute() method is part of the AsyncTask class.
For those newbies learning to use the AsyncTask, and for those oldies who forgot how to use it, this is a very simple/straight forward example of us performing a background task on a list of strings.
The onPreExecute runs on its callers thread, that is the thread that called execute(), and is called before the doInBackground method is called and it runs a background thread.
The doInBackground returns some result which is then received in the onPostExecute, which runs in the main thread.
Thread
private fun runThreadExample() {
val thread = Thread({
// The code that runs in here will be running in a separate thread
Log.d(TAG, "runThreadExample(): ${Thread.currentThread().name}")
})
thread.start()
}
The code is very straight forward and self explanatory. A thread value is created, with a runnable passed in. The code inside the runnable gets called on a separate thread.
Handler
private fun runHandlerExample() {
val handlerThread = HandlerThread("handlerThreadName")
handlerThread.start() // you must first start the handler thread before using it
val handler = Handler(handlerThread.looper)
handler.post({
// runs on background thread with the name provided above (e.g 'handlerThreadName')
Log.d(TAG, Thread.currentThread().name)
})
}
Handler usage is just as easy as Thread. A handler runs on a handler thread. The HandlerThread is a direct sub class of Thread (extends it) that takes in a thread name, and/or a priority using one of its two constructor methods. Just like the name suggests, this type of thread is for Handlers. The HandlerThread provides a looper which is used by its handler to to run a piece of code in the looper specified. The Handler uses this looper to determine what thread to run on (Note if no looper is passed in, it will just run on the current threads looper — in other words it will just run in the same thread it was called from).
Timer
private fun runTimerExample() {
val timer = Timer() // can also be used to schedule tasks at an interval
timer.schedule(object : TimerTask() { // Schedules the specified task for execution after the specified delay.
override fun run() {
// Runs on background thread
Log.d(TAG, "runTimerExample(): ${Thread.currentThread().name}")
timer.cancel() // Terminates this timer, discarding any currently scheduled tasks.
timer.purge() // Removes all cancelled tasks from this timer's task queue.
}
}, 0) // delay in milliseconds
}
Another easy threading example, the Timer class is a facility for threads to schedule tasks for future execution in a background thread. Tasks may be scheduled for one-time execution, or for repeated execution at regular intervals.
RxJava
Time to step it up a noch.
private fun runRxJavaExample() {
// For this example lets use a string array and rx maps as the
// methods used to run on separate threads and log the thread names out
Observable.fromArray(arrayOf("test1", "test2"))
.map {
// Runs on scheduler/background thread
Log.d(TAG, "First (background) map function: ${Thread.currentThread().name}")
}
.map {
// Runs on scheduler/background thread
Log.d(TAG, "Second (background) map function: ${Thread.currentThread().name}")
}
// anything before this runs on the Schedulers.io thread
.subscribeOn(Schedulers.io())
// anything after this runs on the Schedulers.computation() thread
.observeOn(Schedulers.computation())
.map {
// Runs on computation/background thread
Log.d(TAG, "Third (Computation) map function: ${Thread.currentThread().name}")
}
// anything after this runs on the AndroidSchedulers.mainThread()
.observeOn(AndroidSchedulers.mainThread())
.map {
// Runs on UI thread
Log.d(TAG, "Fourth (UI/main) map function: ${Thread.currentThread().name}")
}
.subscribe({
// Runs on UI/main thread
Log.d(TAG, "runRxJavaExample().subscribe: ${Thread.currentThread().name}")
})
}
The comments are very clear but there is something that we need to play closer attention to. Take a look at the subscribeOn and the observeOn calls. By carefully analyzing what is going we, we can see that subscribeOn runs every call that was made before it was called on the specified thread, and the observeOn calls everything that was called after in the specified thread. What happens if we call subscribeOn AFTER an observeOn call?
Lets take a look at this code and what gets printed out
private fun runRxJavaExample2() {
Observable.fromArray(arrayOf("test1", "test2"))
.observeOn(AndroidSchedulers.mainThread())
.map {
Log.d(TAG, "Fourth (UI/main) map function: ${Thread.currentThread().name}")
}
.subscribeOn(Schedulers.io())
.subscribe({
Log.d(TAG, "runRxJavaExample().subscribe: ${Thread.currentThread().name}")
})
}
This prints the following:
E/MainActivity: Map function: main
E/MainActivity: runRxJavaExample2().subscribe: main
From looking at the log we can see that the SubscribeOn has no effect after an observeOn has been called. This is very important to keep in mind, when i started with RxJava, i made this exact same mistake and being an Rx noobie, took me sometime to figure out. The reason for this is because
Theres a great article talking about this more in depth here.
Future
Introduced in Java 5, a Future
But this class has been improved a couple of times such as in Googles Guava library as a ListenableFuture, and also CompletableFutures (Java 8). Listenable and Completable futures are futures that allow the users to chain calls similar to RxJava and its operators.
Here is an example of what a normal Future (Java 5) implementation looks like
class FuturesExample {
private val executorService = Executors.newFixedThreadPool(1)
companion object {
private val TAG = FuturesExample::class.java.simpleName
}
fun execute() {
// Runs a task on a background thread. For running multiple task,
// you can use executorService.invokeAll() which takes a array of callable's
val future = executorService.submit {
// runs in background pool thread
Log.d(TAG, "execute(): " + Thread.currentThread().name)
}
// As long as the task is still happening, do not ove on
while (!future.isDone) {
// wait 1 second
SystemClock.sleep(TimeUnit.SECONDS.toMillis(1))
}
// We are now done, time to shut down
executorService.shutdown()
}
}
To run the future we just do
FuturesExample().execute()
CompletableFutures example:
A CompletableFuture future is a Future that may be explicitly completed (setting its value and status), and may be used as a CompletionStage, supporting dependent functions and actions that trigger upon its completion.
class CompletableFuturesExample {
companion object {
private val TAG = CompletableFuturesExample::class.java.simpleName
}
fun execute() {
val executorService = Executors.newSingleThreadExecutor()
// create a supplier object to start with
val supplier = Supplier { "Hello World!" }
// This function takes some text and returns its length
val function = Function<String, Int> { text -> text.length }
// requires api 24+ (N)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
CompletableFuture
.supplyAsync(supplier, executorService) // provide the initial supplier and specify executor
.thenApply(function) // do something
.whenComplete { int, _ ->
Log.d(TAG, "$int") // returns the length of 'Hello World!'
executorService.shutdown() // complete
}
}
}
}
and same as the future, to use it we just call the execute() method
private fun runFuturesExample() {
CompletableFuturesExample().execute()
}
Very similar to RxJava isn’t it? Kinda fun to play around with actually.
For the curious ones, there is a nice guide on CompletableFutures here.
More:
There are other asynchronous APIs you can play with such as Java 9’s Reactive Streams, IntentServices, and Loaders, Job Schedulers, GcmNetWorkManager, Android Job (one of my favorites), Sync adapters, and many more.
Complete source code can be found here.
Thank you for reading, Thats all for now!