Note that by default, on Android, everything is on the main thread (also called, the UI thread
).
In order to do heavy task or network calls, you must be on another thread to avoid to block the UI thread.
Once your work done on non-main-thread, you will need to come back on the main thread in order to update the UI.
Keep in mind that every Activity
, View
or Fragment
will crash if UI is modified on a non-main-thread.
- This document is to highlight some way to work with asynchronous on Android.
- Not vocation to be exhaustive and explicit
- The goal is for you to pick the good one.
-
- Setup
-
- Come back on main thread
-
- Thread
-
- WorkerThread
-
- Coroutine
-
- Library specific
-
- Conclusion
All following technics will use this setup: methods and interface.
interface Listener {
@MainThread
fun onCompleted()
}
@MainThread
fun startWorkAsynchronous(listener: Listener) {
// Will be fill in following sections
// the goal will be to call `listener.onCompleted()` once completed on the mainThread.
}
@WorkerThread
private fun startWorkSynchronous() {
// TODO with your eavy computation
}
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post {
// You are here on the main thread
}
Easier way to have an asynchronous method to do eavy job:
private val mainHandler by lazy { Handler(Looper.getMainLooper()) }
@MainThread
fun startWorkAsynchronous(listener: Listener) {
val thread = Thread(Runnable {
startWorkSynchronous()
mainHandler.post(Runnable {
listener.onCompleted()
})
}).start()
}
Attention:
- With thread implemented like that, each call to
startWorkAsynchronous(Listener)
will create a new thread. - Often, a best practice will be to avoid to create multple times thread with someting like
if (doningAsyncJob) { return; }
private val handlerThread by lazy { createHandlerThread() }
private val handler by lazy { Handler(handlerThread.looper) }
private val mainHandler by lazy { Handler(Looper.getMainLooper()) }
@MainThread
fun startWorkAsynchronous(listener: Listener) {
handler.post(Runnable {
startWorkSynchronous()
mainHandler.post(Runnable {
listener.onCompleted()
})
})
}
private fun createHandlerThread(): HandlerThread {
val handlerThread = HandlerThread("example-thread-name")
handlerThread.start()
return handlerThread
}
Often too complicated if your only goal is to do that (you will import too much extra deps for nothing).
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7")
@MainThread
fun startWorkAsynchronous(listener: Listener) {
GlobalScope.launch(Dispatchers.Default) {
startWorkSynchronous()
CoroutineScope.launch(Dispatchers.Main) {
listener.onCompleted()
}
}
}
PS: Even if suspend
is part of Kotlin language, try not spread it accross your project.
With "Square" libraries that handle network call, there are 2 possibilities to make call.
One synchronous, that should not be done on the main thread (use one of the above method to call it).
val okHttpResponse = okHttpClient.newCall(okHttpRequestBuilder.build()).execute()
Otherwise, you can use the library to handle the asynchronism like that
okHttpClient.newCall(okHttpRequestBuilder.build()).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
TODO("Not yet implemented")
}
override fun onResponse(call: Call, response: Response) {
TODO("Not yet implemented")
}
})
A lot of methods exist to make asynchronous code. Each have PROs and CONs depending of your usecase.
Try to avoid well known "callback hell" in your code once you will need to chain multiple asynchronous tasks. A good approach, is to write @WorkerThread
synchronous method and use this method from a non-main-thread to have only one callback when the "chain" is complete.
Other articles and projects on Mercandj.