Do it Asynchronously but Intelligently!!!
The Android platform provides us with many asynchronous techniques to help us run tasks both concurrently and off the UI thread. They help us in implementing a fast and responsive application, but we always have to make choices about which techniques to use. Sometimes its easy to decide — e.g., ThreadPoolExecutor for making multiple HTTP requests with an upper limit, CursorLoader when reading data from a provider-but often we face situations where several mechanisms could solve the problem. In those situations, it’s a natural habit to fallback on a mechanism we know and have used before. This may, however, be a suboptimal choice, and we should aim for something better, based on a set of conditions about each technique:
- It uses the fewest system resources — i.e., CPU cycle and memory.
- It provides the application with maximum performance and responsiveness.
- It allows low implementation effort.
- It provides a good code design — i.e., easy to understand and maintain.
Asynchronous Mechanisms in the Android Platform. Let’s take a look at the relationships among the mechanisms as shown above. The Thread is the fundamental entity of all mechanisms. Features supporting the asynchronous execution are added further up in the hierarchy, each invoking the following features. When selecting the proper asynchronous mechanism, the rule of thumb is to move as high up in the hierarchy as possible to utilise the added platform functionality. When you need to give more execution control to the application, you can move to a lower level, where that control is provided.
Let’s start with Threads
All asynchronous techniques in Android are based on java.lang.Thread class. These techniques make our life easier by managing all the low-level threading. A single thread can be a good choice for occasional one-shot tasks, like initialising an Activity with data from a network resource or storing data on the file system when Activity is destroyed, but execution with Thread doesn’t scale well. If there are many tasks to execute, a thread-per-task pattern can cause memory exhaustion and reduced performance.
Resource Management with ThreadPools
Thread pools are a part of Java’s Executor framework that helps us with thread execution and resource management. Each pool manages a set of worker threads, making the number of threads and dynamics configurable. Threads that have finished executing a task can remain idle in the pool, waiting for the next task to execute. Hence, the thread pool can keep required memory usage within bounds. The thread pool achieves this by queuing the tasks that are added while all threads in the pool are occupied by other tasks.
Thread pools are powerful asynchronous processors with a flexible setup. They are typically used when the application should execute multiple tasks concurrently while limiting the number of threads — for example, when the application uses multiple HTTP connections that should have an upper limit.
Message Communication using HandlerThread & AsyncTask
Both the Thread and the Executor framework are Java mechanisms and have no built-in support for Android message passing. The most common use cases for asynchronous execution involve message passing so that data can be passed between the UI thread and background threads.
The HandlerThread combines the execution of a Thread with the possibility of receiving and processing messages sequentially. This makes the HandlerThread a good alternative for thread-safe execution of dependent tasks.
The sequential execution limitation of the HandlerThread is solved by AsyncTask. The AsyncTask allows background task execution in combination with thread communication, offering a generic and adaptable asynchronous technique. However, it also has some concerns :
- Global execution environment
- Differences between execution types depending on platform versions
Avoid Unexpected Task Termination with Services
The system doesn’t take running threads into consideration when shutting down applications to reclaim system resources. The application termination is based on process rank, and a running Service raises the application process rank. Therefore, use of a Service helps by reducing the risk that the application and its threads are terminated when the system is low on resources.
By moving the asynchronous execution to a Service, you can achieve several advantages:
- Critical background tasks execute independently of the application’s visibility on the screen.
- The asynchronous execution can easily be shared between multiple clients, such as Activities.
- The asynchronous execution can be triggered both via Intents and through method calls, or even across processes via IPC.
For services that should execute tasks sequentially, you can use IntentService, an extension to Service containing a HandlerThread. It has the lifecycle and process rank advantages of a Service combined with a handoff mechanism.
Note: When asynchronous execution is required from a BroadcastReceiver, the IntentService should be the first mechanism you consider.
Easy Access to ContentProviders
Another — more capable — reader of provider data is the CursorLoader. It is both simple and powerful, not requiring much effort to implement. It connects to a provider and observes the content so that new data can be loaded asynchronously to an Activity or Fragment, enabling it to easily update the UI.
For other data sources — where you have to implement a custom loader — a loader isn’t necessarily the best choice. Custom loaders can be a good choice when the following conditions are fulfilled:
- The underlying content is easily observable.
- There should be an easy way to use a data cache.
- Started tasks don’t need to be finished, because the tight coupling of the loader with the Activity and Fragment lifecycles will destroy the attached Loader objects and lose the result.
Thus, it isn’t advisable, for example, to use loaders for network requests; they are not easily observed and they will be interrupted based on the client lifecycle. Data loading that should execute until completion should use a Service or IntentService instead.
Check out all the top articles at blog.mindorks.com