The Java Virtual Machine, or JVM for short, supports multithreading. Any process you run on it is free to create a reasonable number of threads to perform multiple tasks asynchronously. However, writing code that can do so in an optimal and error-free manner can be extremely hard. Over the years, Java, other JVM languages, and a lot of third-party libraries have tried to come up with creative and elegant approaches to address this problem.
For instance, Java 5 introduced the executor framework, which allows you to decouple thread management details from your business logic. Java 8 offers parallel streams, which can easily be used with lambda expressions. RxJava brings reactive extensions to Java, allowing you to write very concise and readable asynchronous code.
Kotlin supports almost all of those approaches and offers a few of its own. In this tutorial, I’ll show you how you can use them in Android apps.
Prerequisites
To be able to follow this tutorial, you’ll need:
- Android Studio 2.3.3 or higher
- Kotlin plugin 1.1.51 or higher
- a basic understanding of Java threads
If you aren’t comfortable working with lambda expressions and SAM interfaces, I suggest you also read the following tutorial before proceeding:
-
Android SDKQuick Tip: Write Cleaner Code With Kotlin SAM Conversions
You can also learn all the ins and outs of the Kotlin language in our Kotlin From Scratch series.
-
Kotlin From Scratch: Nullability, Loops, and Conditions
-
Kotlin From Scratch: More Fun With Functions
1. Creating Threads
Usually, instances of classes that implement the Runnable
interface are used to create threads in Kotlin. Because the Runnable
interface has just one method, the run()
method, you can leverage Kotlin’s SAM conversion feature to create new threads with minimal boilerplate code.
Here’s how you can use the thread()
function, which is a part of Kotlin’s standard library, to quickly create and start a new thread:
thread { // some long running operation }
The above approach is appropriate only when you need to occasionally spawn a thread or two. If concurrency is an important part of your app’s business logic and you need a large number of threads, using thread pools with an executor service is a better idea.
For example, the following code uses the newFixedThreadPool()
method of the Executors
class to create a thread pool containing eight reusable threads and runs a large number of background operations on it:
val myService:ExecutorService = Executors.newFixedThreadPool(8) var i = 0 while (i < items.size) { // items may be a large array val item = items[i] myService.submit { processItem(item) // a long running operation } i += 1 }
It might not be obvious at first glance but, in the above code, the argument to the submit()
method of the executor service is actually a Runnable
object.
2. Getting Results From Threads
Background tasks created using the Runnable
interface cannot return any results directly. If you want to receive results from your threads, you must use the Callable
interface instead, which is also a SAM interface.
When you pass a Callable
object to the submit()
method of an executor service, you receive a Future
object. As its name suggests, the Future
object will contain the result of the Callable
at some point in the future, when the executor service has finished running it. To get the actual result from a Future
object, all you need to do is call its get()
method—but beware, your thread will block if you call it prematurely.
The following sample code shows you how to create a Callable
object that returns a Future
of type String
, run it, and print its result:
val myService:ExecutorService = Executors.newFixedThreadPool(2) val result = myService.submit(Callable{ // some background operation that generates // a string }) // Other operations // Print result Log.d(TAG, result.get())
3. Synchronizing Threads
Unlike Java, Kotlin doesn't have the synchronized
keyword. Therefore, to synchronize multiple background operations, you are expected to use either the @Synchronized
annotation or the synchronized()
standard library inline function. The annotation can synchronize an entire method, and the function works on a block of statements.
// a synchronized function @Synchronized fun myFunction() { } fun myOtherFunction() { // a synchronized block synchronized(this) { } }
Both the @Synchronized
annotation and the synchronized()
function use the concept of monitor locks.
If you don't already know, every object on the JVM has a monitor associated with it. For now, you can think of a monitor as a special token that a thread can acquire, or lock, to gain exclusive access to the object. Once an object's monitor is locked, other threads that want to work on the object will have to wait until the monitor is released, or unlocked, again.
While the @Synchronized
annotation locks the monitor of the object the associated method belongs to, the synchronized()
function can lock the monitor of any object that's passed to it as an argument.
4. Understanding Coroutines
Through an experimental library, Kotlin offers an alternative approach to achieve concurrency: coroutines. Coroutines are far lighter than threads and are much easier to manage.
In mobile multithreaded applications, threads are usually used for operations such as fetching information from the Internet or querying databases. Such operations don't involve much computation, and the threads spend most of their lifetime in a blocked state, just waiting for data to come from somewhere else. As you can probably tell, that's not a very efficient way to use the CPU.
Coroutines are designed to be used instead of threads for such operations. The most important thing you need to understand about coroutines is that they are suspendable. In other words, instead of blocking, they can simply stop when necessary and seamlessly continue later. This leads to much better CPU utilization. Indeed, with well-designed coroutines, you can effortlessly run dozens of background operations.
To be able to use coroutines in your Android Studio project, make sure you add the following compile
dependency in the app
module's build.gradle file:
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.19.3'
5. Creating Suspending Functions
A coroutine can be suspended only with the help of a suspending function. Therefore, most coroutines have calls to at least one such function inside them.
To create a suspending function, all you need to do is add the suspend
modifier to a regular function. Here's a typical suspending function executing an HTTP GET request using the khttp library:
suspend fun fetchWebsiteContents(url: String):String { return khttp.get(url).text }
Note that a suspending function can be called only by a coroutine or another suspending function. If you try calling it from anywhere else, your code will fail to compile.
6. Creating Coroutines
When it comes to creating a new coroutine, Kotlin's standard library has enough coroutine builders to make you feel spoiled for choice. The simplest coroutine builder you can use is the launch()
function, and like most other coroutine builders, it expects a suspending lambda, which is nothing but an anonymous suspending function. As such, this lambda is what becomes the coroutine.
The following code creates a coroutine that makes two sequential calls to the suspending function we created in the previous step:
val job1 = launch { val website1 = fetchWebsiteContents("https://code.tutsplus.com") val website2 = fetchWebsiteContents("https://design.tutsplus.com") }
The return value of the launch()
function is a Job
object, which you can use to manage the coroutine. For example, you can call its join()
method to wait for the coroutine to complete. Similarly, you can call its cancel()
method to immediately cancel the coroutine.
Using the launch()
function is much like creating a new thread with a Runnable
object, primarily because you can't return any value from it. If you want to be able to return a value from your coroutine, you must create it using the async()
function instead.
The async()
function returns a Deferred
object, which, just like the Job
object, allows you to manage the coroutine. However, it also allows you to use the await()
function to wait for the result of the coroutine without blocking the current thread.
For instance, consider the following coroutines that use the fetchWebsiteContents()
suspending function and return the content lengths of two different webpage addresses:
val jobForLength1 = async { fetchWebsiteContents("https://webdesign.tutsplus.com").length } val jobForLength2 = async { fetchWebsiteContents("https://photography.tutsplus.com").length }
With the above code, both the coroutines will start immediately and run in parallel.
If you now want to use the returned lengths, you must call the await()
method on both the Deferred
objects. However, because the await()
method too is a suspending function, you must make sure that you call it from another coroutine.
The following code shows you how to calculate the sum of the two lengths using a new coroutine created with the launch()
function:
launch { val sum = jobForLength1.await() + jobForLength2.await() println("Downloaded $sum bytes!") }
7. Using Coroutines in the UI Thread
Coroutines do make use of background threads internally, which is why they don't run on an Android app's UI thread by default. Consequently, if you try modifying the contents of your app's user interface from inside a coroutine, you will encounter a runtime error. Fortunately, it's trivially easy to run a coroutine on the UI thread: you just have to pass the UI
object as an argument to your coroutine builder.
For example, here's how you can rewrite the last coroutine to display the sum inside a TextView
widget:
launch(UI) { val sum = jobForLength1.await() + jobForLength2.await() myTextView.text = "Downloaded $sum bytes!" }
The above code might seem mundane at first, but look again. It's not only able to wait for two background operations to complete without using callbacks, it's able to do so on the app's UI thread without blocking it!
Having the ability to wait on the UI thread, without making your UI feel sluggish or triggering an Application Not Responding error, often referred to as ANR, simplifies a lot of otherwise complex tasks.
For instance, with the suspending delay()
function, which is the non-blocking equivalent of the Thread.sleep()
method, you can now create animations with loops. To help you get started, here's a sample coroutine that increments the x coordinate of a TextView
widget every 400 ms, thus creating a marquee-like effect:
launch(UI) { while(myTextView.x < 800) { myTextView.x += 10 delay(400) } }
Conclusion
While developing Android apps, it is imperative that you perform long-running operations in background threads. In this tutorial, you learned several approaches you can follow to create and manage such threads in Kotlin. You also learned how to use the still experimental coroutines feature to wait inside threads without blocking them.
To learn more about coroutines, you can refer to the official documentation. And while you're here, check out some of our other posts about Kotlin and Android development!
-
Android SDKHow to Create an Android Chat App Using Firebase
-
Android SDKSending Data With Retrofit 2 HTTP Client for Android
-
Android SDKGet Started With RxJava 2 for Android
-
Android SDKJava vs. Kotlin: Should You Be Using Kotlin for Android Development?
Powered by WPeMatico