Thinking About Threads and LiveData

Users like apps that run smoothly. Users do not like applications that feel sluggish.

Usually, the reason why apps feel sluggish is due to problems with thread management, particularly when performing some form of I/O (disk, database, network, etc.). We need to take care when performing I/O that we do not slow down the UI of the app, while still getting the data that we need to display.

Nowadays, working with background threads is often done in conjunction with some form of reactive programming. In reactive programming, we request for work to be done and arrange to get control when it is completed. That work can be performed on one thread, while we get the results on some other thread. In particular, reactive programming attempts to hide a lot of that thread complexity, making it seem like we are doing “normal” programming.

This chapter introduces the key threading issues with Android and explores the Jetpack solution for reactive programming: LiveData. Mostly, this chapter is to “set the stage” for exploring I/O in upcoming chapters.

The Main Application Thread

When you call setText() on a TextView, you probably think that the screen is updated with the text you supply, right then and there.

That’s not really what happens.

Rather, everything that modifies the widget-based UI goes through a message queue. Calls to setText() do not update the screen — they just place a message on a queue telling the operating system to update the screen. The operating system pops these messages off of this queue and does what the messages require.

The queue is processed by one thread, variously called the “main application thread” and the “UI thread”. So long as that thread can keep processing messages, the screen will update, user input will be handled, and so on.

However, the main application thread is also used for nearly all callbacks into your activity. Your onCreate(), onClick(), onCreateView(), and similar functions are all called on the main application thread. While your code is executing in these functions, Android is not processing messages on the queue, and so the screen does not update, user input is not handled, and so on.

This, of course, is bad. So bad, that if you take more than a few seconds to do work on the main application thread, Android may display the dreaded “Application Not Responding” dialog (ANR for short), and your activity may be killed off.

Nowadays, though, the bigger concern is jank.

“Jank”, as used in Android, refers to sluggish UI updates, particularly when something is animating. For example, you may have encountered some apps that when you scroll in the app, the content does not scroll smoothly. Rather, it scrolls jerkily, interleaving periods of rapid movement with periods where the scrolling animation is frozen. Most of the time, this is caused by the app’s author doing too much work on the main application thread.

Android widget-based UIs are updated at 60 frames per second. This means that any given frame needs to be assembled in less than 16ms. Since the framework and OS have work that needs to be done too, that means our application code must run in a lot less than 16ms per frame. And, since our app may be called on many times in a frame, this means that any given callback function, such as onBindViewHolder() of a RecyclerView, needs to be done very quickly, best measured in microseconds. If, instead, we take several milliseconds, we will “drop frames” (i.e., the screen does not get updated between two successive frames), and the user may perceive jank as a result.

Hence, you want to make sure that all of your work on the main application thread happens quickly. This means that anything slow should be done in a background thread, so as not to tie up the main application thread. This includes things like:


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.