Android apps handle all the UI processing on what is called either the UI or main thread -- they are the same. Because we design apps that do computationally demanding tasks (e.g., manipulate data such as traces on a map) or interact with components that introduce significant delay or latency (e.g., saves to a local database, syncing the local database with the cloud), we need to move high latency operations off the main thread -- else, the user will remove the app because it is non-responsive to UI interaction. If you don't do this then Android will inform the user via a dialogy:``Application Not Responding".
Imagine the user clicks on a button but the UI/main thread can't get to it because it's downloading a chapter of a book! That's poor design. So the user waits. Wonders what is going on with this shiny new app. Gets friustrated and utimately bins your new app you've spent a thousand hours developing.
We can fix this by shifting computationally intensive tasks or latency inducing operations off the main thread. Here's how we do that.
Before we dive into the code, there is an important concept you need
to know about how Android is set to update Views upon a program calling
UI-related functions (e.g., textView.text = "new text
"). Every time the
system receives a request to update Views, it pushes the request into a
message queue, which operates in a first-come-first-serve manner. Views
get updates upon the corresponding request popping off the queue. The
system maintains a message queue for each thread so be sure to post
your request to the right message queue (more on this later). It is
often the case that a worker thread (not the UI one) needs to update
Views. For example, a worker thread handling the download of a large
file needs to update a progress bar shown on the screen. In such
situations, be sure to NOT call UI-related functions in the worker
thread as it may crash your program. Instead, you need to put the
UI-related functions inside a runnable and post it into the message
queue of the UI thread using a Handler. Now let's look at the first
demo app. This code has a number of neat examples of using Threads,
Handlers, and Runnables. It covers:
First, runOnUiThread()
:
where you can run a runnable on the UI. Posting using runOnUiThread()
and Handler are the same except that if you call runOnUiThread() inside
the UI thread, the corresponding Views will be executed right away
without waiting inside the message queue.
The second example in this demo code is the use of a runnable being
posted into a message queue using a Handler. When a Handler object is
created, you need to specify which message queue it handles. Here we use Looper.getMainLooper()
to indicate that the request needs to be posted into the UI thread.
fun startThread(view: View) {
val countDownThread = Thread(){
var handler = Handler(Looper.getMainLooper())
var blank_button_runnable = Runnable { blank_button.text = "posting from background thread" }
var i = 0
handler.post(blank_button_runnable)
while (i < 1000) {
try {
val countdown_button_runable = Runnable { count_button.text = "#: $i" }
runOnUiThread(countdown_button_runable)
Thread.sleep(80)
} catch (e: Exception) { }
i++
}
}
countDownThread.start()
}
The final example in this code includes two helper methods that
handle the callback from pressing the button. One of the methods called
longTask() blocks the UI thread to simulate a long running task. The
other method spawns new threads for every
click on the bottom. Comment and uncomment the longTask() method inside
the startWaitThread method to see the
behavior.
fun startWaitThread(view: View) {
//longTask() //uncomment this line to block the UI thread
val runLongTask = Runnable { longTask() }
val thread = Thread(runLongTask)
thread.start()
}
fun longTask() {
try {
Thread.sleep(2000)
} catch (e: java.lang.Exception) { }
}
This code shows how to use the ViewMode and LiveData to handle the communication between a worker and the UI thread.
We use the MVVM architecture in the development this code. The main benefit of MVVM in this example is to ensure that the date shown in a ListView do not get lost upon configuration change. This is because there is no data stored in the View layer so nothing gets lost if the Activity/Fragment is destroyed.