Android thread model
Within an Android process, there may be many threads of execution. Each thread is a separate sequential flow of control within the overall program—it executes its instructions in order, one after the other, and they also share allocated slices of CPU time managed by the operating system task scheduler.
While the application process is started by the system and prevented from directly interfering with data in the memory address space of other processes, the threads may be started by an application code and can communicate and share data with other threads within the same process. Apart from the shared data that all the threads share in the same process, a thread can use its own memory cache to store its data in its own memory space.
The main thread
When the application process starts, apart from DVM housekeeping threads, the system creates a thread of execution called main
. This thread, as the name explains, plays a crucial role in the application lifetime as it is the thread that interacts with the Android UI components, updating the state and their look on the device screen.
Moreover, by default, all the Android application components (Activity
, Service
, ContentProvider
, and BroadcastsReceiver
) are also executed over the main thread line of execution. The following image shows the lists of threads running inside an application process with the main thread at the top of the list with a unique thread ID (TID) assigned by the system:
The main thread, also known as UI Thread, is also the thread where your UI event handling occurs, so to keep your application as responsible as possible, you should:
- Avoid any kind of long execution task, such as input/output (I/O) that could block the processing for an indefinite amount of time
- Avoid CPU-intensive tasks that could make this thread occupied for a long time
The following diagram displays the main interactions and components involved in the Looper
line of execution thread:
The UI/Main thread, which has a Looper
facility attached to it, holds a queue of messages (MessageQueue
) with some unit of work to be executed sequentially.
When a message is ready to be processed on the queue, the Looper Thread pops the message from the queue and forwards it synchronously to the target handler specified on the message.
When the target Handler
finishes its work with the current message, the Looper
thread will be ready to process the next message available on the queue. Hence, if the Handler
spent a noticeable amount of time processing the message, it will prevent Looper
from processing other pending messages.
For example, when we write the code in an onCreate()
method in the Activity
class, it will be executed on the main thread. Likewise, when we attach listeners to user-interface components to handle taps and other user-input gestures, the listener callback executes on the main thread.
For apps that do little I/O or processing, such as applications that don't do complex math calculations, don't use the network to implement features, or don't use filesystem resources, this single thread model is fine. However, if we need to perform CPU-intensive calculations, read or write files from permanent storage, or talk to a web service, any further events that arrive while we're doing this work will be blocked until we're finished.
Note
Since the Android 5.0 (Lollipop), a new important thread named RenderThread was introduced to keep the UI animations smooth even when the main thread is occupied doing stuff.
The Application Not Responding (ANR) dialog
As you can imagine, if the main thread is busy with a heavy calculation or reading data from a network socket, it cannot immediately respond to user input such as a tap or swipe.
An application that doesn't respond quickly to user interaction will feel unresponsive—anything more than a couple of hundred milliseconds delay is noticeable. This is such a pernicious problem that the Android platform protects users from applications that do too much on the main thread.
Note
If an app does not respond to user input within five seconds, the user will see the Application Not Responding (ANR) dialog and will be offered the option to quit the application.
The following screenshot shows a typical Android ANR dialog:
Android works hard to synchronize the user interface redraws with the hardware-refresh rate. This means that it aims to redraw at the rate of 60 frames per second—that's just 16.67 ms per frame. If we do work on the main thread that takes anywhere near 16 ms, we risk affecting the frame rate, resulting in jank—stuttering animations, jerky scrolling, and so on.
Ideally, of course, we don't want to drop a single frame. Jank, unresponsiveness, and especially the ANR, offer a very poor user experience, which translates into bad reviews and unpopular applications. A rule to live by when building Android applications is: do not block the main thread!
Note
Android provides a helpful strict mode setting in Developer Options on each device, which will flash on the screen when applications perform long-running operations on the main thread.
Further protection was added to the platform in Honeycomb (API level 11) with the introduction of a new Exception
class, NetworkOnMainThreadException
, a subclass of RuntimeException
that is thrown if the system detects network activity initiated on the main thread.
Maintaining responsiveness
Ideally then, we may want to offload any long-running operations from the main thread so that they can be handled in the background by another thread, and the main thread can continue to process user-interface updates smoothly and respond in a timely fashion to user interactions.
The typical time-consuming tasks that should be handled on a background thread include the following:
- Network communications
- Input and output file operations on the local filesystem
- Image and video processing
- Complex math calculations
- Text processing
- Data encoding and decoding
For this to be useful, we must be able to coordinate the work and safely pass data between cooperating threads—especially between background threads and the main thread, and it is exactly to solve this problem that asynchronous programming is used.
Let's get started with the synchronous versus asynchronous diagram:
The preceding example graphically shows the main differences between the two models of processing. On the left-hand side, the data download task occurs on the main thread, keeping the thread busy until the download data is finished. So if the user interacts with the UI and generates an event such as a touch event, the application will suffer a lag or will become unresponsive if the download task takes a substantial amount of time to finish.
On the right-hand side, the asynchronous model will hand over the download data task to another background thread, keeping the main thread available to process any event coming from the UI interaction. When the downloaded data is available, the background task could post the result to the main thread if the data handling needs to update any UI state.
When we use an asynchronous model to program our application, the Android OS will also take advantage of additional CPU cores available in the most recent devices to execute multiple background threads at the same time and increase the application's power efficiency.
Note
This simultaneous execution of separate code paths that potentially interact with each other is known as concurrency.
The simultaneous execution of subunits of work in parallel to complete one unit of work is known as parallelism.