Asynchronous Android Programming - Second Edition

4.5 (2 reviews total)
By Helder Vasconcelos
    Advance your knowledge in tech with a Packt subscription

  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Asynchronous Programming in Android

About this book

Asynchronous programming has acquired immense importance in Android programming, especially when we want to make use of the number of independent processing units (cores) available on the most recent Android devices. With this guide in your hands you’ll be able to bring the power of Asynchronous programming to your own projects, and make your Android apps more powerful than ever before!

To start with, we will discuss the details of the Android Process model and the Java Low Level Concurrent Framework, delivered by Android SDK. We will also guide you through the high-level Android-specific constructs available on the SDK: Handler, AsyncTask, and Loader. Next, we will discuss the creation of IntentServices, Bound Services and External Services, which can run in the background even when the user is not interacting with it. You will also discover AlarmManager and JobScheduler APIs, which are used to schedule and defer work without sacrificing the battery life. In a more advanced phase, you will create background tasks that are able to execute CPU-intensive tasks in a native code-making use of the Android NDK. You will be then guided through the process of interacting with remote services asynchronously using the HTTP protocol or Google GCM Platform. Using the EventBus library, we will also show how to use the Publish-Subscribe software pattern to simplify communication between the different Android application components by decoupling the event producer from event consumer.

Finally, we will introduce RxJava, a popular asynchronous Java framework used to compose work in a concise and reactive way. Asynchronous Android will help you to build well-behaved applications with smooth responsive user interfaces that delight the users with speedy results and data that’s always fresh.

Publication date:
July 2016
Publisher
Packt
Pages
394
ISBN
9781785883248

 

Chapter 1. Asynchronous Programming in Android

Asynchronous programming has become an important topic of discussion in the past few years, especially when using the concurrent processing capabilities available on the most recent mobile hardware.

In recent years, the number of independent processing units (cores) available on the CPU have increased, so to benefit from this new processing power, a new programming model called asynchronous programming has appeared to orchestrate the work between the several independent hardware-processing units available on the device. Asynchronous programming comes to the rescue to solve the problems that could arise from this new processing paradigm.

Android applications, since they mostly run on devices with multiple units of processing, should take advantage of asynchronous programming to scale and improve the application performance when blocking operations, and when CPU-intensive tasks are required.

Android is an open source operating system (OS) based on Linux kernel that was devised in 2003 by Andy Rubin, Nick Sears, Chris White, and Rick Miner, and then acquired by Google in July, 2005.

The Android OS, actually maintained by Google and the Open Handset Alliance, was created to provide an open mobile-device platform for devices with limited resources of computation, memory, and energy.

The platform has been incorporating advanced mobile devices standards, such as NFC and Bluetooth LE, and its scope has grown from a pure smartphone platform to a broader software platform for smart watches, TVs, tablets, and consoles.

The maintainers have been regularly updating the platform with great features and some improvements over minor and major releases since the first release.

The following diagram displays the Android versions over time:

 

Android software stack


Android software stack (C libraries and Java frameworks), orchestrated by the Android runtime (Dalvik VM, and most recently, ART) was created around the Linux kernel to provide highly interactive user experiences over a well-proven group of technologies.

In each new OS version, a well-defined application interface (API) is provided to the developer in order to create applications around the new features and standards introduced with the release.

The Android application compiled code (bytecode), typically a Java compiled code, runs on a virtual machine based on Dalvik or ART.

Dalvik runtime

The Dalvik VM (DVM) runtime, created by Dan Borstein, was the first runtime for the platform and is a register-based virtual machine that was created to run the Java code efficiently in a constrained runtime with a limited amount of power processing, RAM, and electric power.

Dalvik's creators claim that the DVM is, on an average, around 30% more efficient than the standard Java VM (Oracle). According to Bornstein, it requires 30% less instructions and 35 % less coding units.

Clearly, Google has gone to great lengths to squeeze every drop of performance out of each mobile device to help developers build responsive applications.

The virtual machine, which runs the Java code compiled and transformed to the dex format over the dx tool, runs on a Linux process with its own memory space and file descriptors. It also manages its own group of threads.

In more advanced architectures, an Android application might run a service in a separate process and communicate over the IPC mechanism, but most of the time, it runs on a single self-contained process.

The dex file and application resources are packed in an Android application package (APK) by the AAPT and installed over Google Play in the end user devices.

Note

The application store distribution model has become extremely popular on the mobile platforms since the launch of the Apple iPhone in 2007.

Since Android 2.2 the DVM comes with a trace-based Just-In-Time (JIT) compilation feature that actively optimizes every time the application runs some short segments of frequently used bytecode called traces.

The generated machine code provides significant performance improvements in the application execution and on the time spent on some intensive CPU tasks, and thereafter, decreases the battery power used.

ART runtime

The ART runtime is a new version of the DVM and was introduced to improve the runtime performance and memory consumption. The new runtime was introduced in Android 4.4 KitKat as an experimental runtime, and since the Android 5.0 Lollipop, it has become the main Android runtime.

This new runtime, making use of the ahead-of-time (AOT) compilation, brings new app-performance optimizations on startup time and application execution. The AOT, as opposed to DVM JIT (Just in Time), compiles the dex files during the installation time using the device dex2oat tool. The compiled code generated from the dex2oat tool generates system-dependent code for the target device and removes the delay introduced by the JIT compilation during each application execution.

The AOT compiler also reduces the number of processor cycles used by the application as it removes the time spent by the JIT compiler to convert the code into machine code, and then uses less battery power to run the application.

One of the drawbacks of the AOT compilation is the larger memory footprint in comparison with the JIT used by DVM.

With the new runtime, some improvements were also introduced in the memory allocation and on Garbage Collection (GC), resulting in a more responsive UI and better application experience.

Memory sharing and Zygote

Basically, the platform runs an instance of DVM/ART for each application, but large optimization of the platform is brought about by the way a new DVM instance is created and managed.

A special process called the Zygote (first life cell in an animal's reproduction)—the process that all the Android applications are based on—is launched when an Android device initially boots.

The Zygote starts up a virtual machine, preloads the core libraries, and initializes various shared structures. It then waits for instructions by listening on a socket.

When a new Android application is launched, the Zygote receives a command to create a virtual machine to run the application on. It does this by forking its pre-warmed VM process and creating a new child process that shares some memory portions with the parent, using a technique called copy-on-write (COW).

The COW technique, available on most Unix systems, only allocates new memory on the child process when the process tries to change the memory cloned from the parent process.

This technique has some fantastic benefits, as listed in the following:

  • First, the virtual machine and core libraries are already loaded into the memory. Not having to read this significant chunk of data from the filesystem to initialize the virtual machine drastically reduces the startup overhead.

  • Second, the memory in which these core libraries and common structures reside is shared by the Zygote with all other applications, resulting in saving a lot of memory when the user is running multiple apps.

 

Android process model


Android is a multiuser, multitasking system that can run multiple applications in parallel, where all the applications attempt to acquire CPU time to execute its job.

Each application runs independently on an isolated Linux process cloned from the Zygote process, and by default, all the Android components run within the same process with the same name as the application package specified in Android Application Manifest (AAM).

The Linux kernel will fairly allocate small amounts of CPU time for application execution called CPU time slices. This time-slicing approach means that even a single-processor device can appear to be actively working in more than one application at the same time, when in fact, each application is taking very short turns on the CPU.

Process ranks

The Android operating system tries to maintain the application running for as long as possible, but when the available memory is low, it will try to free resources in the system by killing the processes with lower importance first.

This is when process ranking comes into the picture; the Android processes are ranked in the next five categories from the higher priority to the lower priorities:

  • Foreground process: This is a process that hosts an activity or service that the user is currently interacting with: a service started in the foreground or service running its life cycle callbacks

  • Visible process: This is a process that hosts a paused activity or service bounded to a visible activity

  • Service process: This is a process that hosts a service not bound to a visible activity

  • Background process: This is a process that hosts a non-visible activity; all background processes are sorted over a Least-Recently-Used (LRU) list, therefore, the most recently used processes are the last killed processes when they have the same rank

  • Empty process: This is a process used to cache inactive Android components and to improve any component startup time

When the system reaches a point that it needs to release resources, the processes available to be killed will be sorted, taking into account the process rank, last used processes, and components running.

Process sandboxing

The Android application always runs under a unique Linux user ID (UID) assigned to the application during the application installation so that the process runs on a sandboxed environment, which by default, isolates your data and code execution from other apps.

In some cases, a user could explicitly be required to share the UID with another application to have access to their data:

USER     PID   PPID  VSIZE  RSS  PC  NAME
root            319   1     1537236 31324 S zygote
….
u0_a221   5993  319   1731636 41504 S com.whatsapp
u0_a96    3018  319   1640252 29540 S com.dropbox.android
u0_a255   4892  319   1583828 34552 S com.accuweather.android…

The preceding table that results from running the adb shell ps command in the computer with Android SDK Table is a list of Android running processes.

The first column shows the user identifier (UID) assigned at the time of installation, the second column is the process ID (PID), the third column shows the parent process ID (PPID) that for Android applications is the Zygote process, and the last column shows the application package.

From this list, we can assure that the WhatsApp application is running under the user ID u0_a221 with the process ID 5993 and the parent process is the Zygote process with the PID 319.

 

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.

 

Concurrency in Android


As explained before, in order to achieve a scalable application in a multicore device environment, the Android developer should be capable of creating concurrent lines of execution that combine and aggregate data from multiple resources.

The Android SDK, as it is based on a subset of Java SDK, derived from the Apache Harmony project, provides access to low-level concurrency constructs such as java.lang.Thread, java.lang.Runnable, and the synchronized and volatile keywords.

These constructs are the most basic building blocks to achieve concurrency and parallelism, and all the high-level asynchronous constructs are created around these building blocks.

The most basic one, java.lang.Thread, is the class that is mostly used and is the construct that creates a new independent line of execution in a Java program:

public class MyThread extends Thread {
    public void run() {
        Log.d("Generic", "My Android Thread is running ...");
    }
}

In the preceding code, we subclassed java.lang.Thread to create our own independent line of execution. When Thread is started, the run method will be called automatically and it will print the message on the Android log:

MyThread myThread = new MyThread();
myTread.start();

At this time, we will create an instance of our MyThread, and when we start it in the second line, the system creates a thread inside the process and executes the run() method.

Other helpful thread-related methods include the following:

  • Thread.currentThread(): This retrieves the current running instance of the thread

  • Thread.sleep(time): This pauses the current thread from execution for the given period of time

  • Thread.getName() and Thread.getId(): These get the name and TID, respectively so that they can be useful for debugging purposes

  • Thread.isAlive(): This checks whether the thread is currently running or it has already finished its job

  • Thread.join(): This blocks the current thread and waits until the accessed thread finishes its execution or dies

The Runnable interface, which is another building block that comes from the Java API, is an interface defined to specify and encapsulate code that is intended to be executed by a Java thread instance or any other class that handles this Runnable:

package java.lang;

public interface Runnable {   
    public abstract void run();
}

In the following code, we basically created the Runnable subclass so that it implements the run() method and can be passed and executed by a thread:

public class MyRunnable implements Runnable {

    public void run(){
        Log.d("Generic","Running in the Thread " +
                        Thread.currentThread().getId());
	// Do your work here
	...
    }
}

Now our Runnable subclass can be passed to Thread and is executed independently in the concurrent line of execution:

Thread thread = new Thread(new MyRunnable());
thread.start();

While starting new threads is easy, concurrency is actually a very difficult thing to do. Concurrent software faces many issues that fall into two broad categories: correctness (producing consistent and correct results) and liveness (making progress towards completion). Thread creation could also cause some performance overhead, and too many threads can reduce the performance, as the OS will have switch between these lines of execution.

Correctness issues in concurrent programs

A common example of a correctness problem occurs when two threads need to modify the value of the same variable based on its current value. Let's consider that we have a myInt integer variable with the current value of 2.

In order to increment myInt, we first need to read its current value and then add 1 to it. In a single-threaded world, the two increments would happen in a strict sequence—we will read the initial value 2, add 1 to it, set the new value back to the variable, and then repeat the sequence. After the two increments, myInt holds the value 4.

In a multithreaded environment, we will run into potential timing issues. It is possible that two threads trying to increment the variable would both read the same initial value 2, add 1 to it, and set the result (in both cases, 3) back to the variable:

int myInt = 2;
...
public class MyThread extends Thread {

    public void run() {
         super.run();
         myInt++;
   }
}
...
Thread t1 = new MyThread();
Thread t2 = new MyThread();
t1.start();
t2.start();

Both threads behaved correctly in their localized view of the world, but in terms of the overall program, we will clearly have a correctness problem; 2 + 2 should not equal 3! This kind of timing issue is known as a race condition.

A common solution to correctness problems, such as race conditions, is mutual exclusion—preventing multiple threads from accessing certain resources at the same time. Typically, this is achieved by ensuring that threads acquire an exclusive lock before reading or updating shared data.

To achieve this correctness, we can make use of the synchronized construct to solve the correctness issue on the following piece of code:

Object lock = new Object();
public class MyThread extends Thread {
    public void run() {
        super.run();
        synchronized(lock) {
            myInt++;
        }
    }
}

In the preceding code, we used the intrinsic lock available in each Java object to create a mutually exclusive scope of code that will enforce that the increment sentence will work properly and will not suffer from correctness issues as explained previously. When one of the threads gets access to the protected scope, it is said that the thread acquired the lock, and after the thread gets out of the protected scope, it releases the lock that could be acquired by another thread.

Another way to create mutually exclusive scopes is to create a method with a synchronized method:

int myInt = 2;
synchronized void increment(){
    myInt++;
}
...
public class IncrementThread extends Thread {
    public void run() {
        super.run();
        increment();
    }
}

The synchronized method will use the object-intrinsic lock, where myInt is defined to create a mutually exclusive zone so IncrementThread, incrementing myInt through the increment(), will prevent any thread interference and memory consistency errors.

Liveness issues in concurrent programs

Liveness can be thought of as the ability of the application to do useful work and make progress towards goals. Liveness problems tend to be an unfortunate side effect of the solution to the correctness problems.

Both properties should be achieved in a proper concurrent program, notwithstanding the correctness is concerned with making progress in a program preventing a deadlock, livelock, or starvation from happening, and the correctness is concerned with making consistent and correct results.

Note

Deadlock is a situation where two or more threads are unable to proceed because each is waiting for the others to do something. Livelock is a situation where two or more threads continuously change their states in response to the changes in the other threads without doing any useful work.

By locking access to data or system resources, it is possible to create bottlenecks where many threads are contending to access a single lock, leading to potentially significant delays.

Worse, where multiple locks are used, it is possible to create a situation where no thread can make progress because each requires exclusive access to a lock that another thread currently owns—a situation known as a deadlock.

Thread coordination

Thread coordination is an important topic in concurrent programming, especially when we want to perform the following tasks:

  • Synchronize access of threads to shared resources or shared memory:

    • Shared database, files, system services, instance/class variables, or queues

  • Coordinate work and execution within a group of threads:

    • Parallel execution, pipeline executions, inter-dependent tasks, and so on

When we want to coordinate thread efforts to achieve a goal, we should try to avoid waiting or polling mechanisms that keep the CPU busy while we wait for an event in another thread.

The following example shows us a small loop where we will continuously occupy the CPU while we wait for a certain state change to happen:

while(!readyToProcess) {
  // do nothing .. busy waiting wastes processor time.
}

To overcome the coordination issue, and to implement our own constructs, we should use some low-level signals or messaging mechanisms to communicate between threads and coordinate the interaction.

In Java, every object has the wait(), notify(), and notifyAll()methods that provide low-level mechanisms to send thread signals between a group of threads and put a thread in a waiting state until a condition is met.

This mechanism, also known as monitor or guard, is a design pattern commonly used in another languages and it ensures that only one thread can enter a given section of code at any given time with an ability to wait until a condition happens.

This design pattern, in comparison with our previous example, delivers a better and efficient CPU-cycle management while waiting for any particular situation to happen on another thread, and is generally used in situations where we need to coordinate work between different lines of execution.

In the following code example, we are going to explain how to use this construct to create a basic multithreaded Logger with 10 threads that will wait in the monitor section until a message is pushed (condition) by any other thread in the application.

The Logger, which is responsible for logging on to the output, has a queue with a maximum of 20 positions to store the new logging text messages:

public class Logger {
    LinkedList<String> queue = new LinkedList<String>();
    private final int MAX_QUEUE_SIZE = 20;
    private final int MAX_THREAD_COUNT = 10;

In the next code, we will create a Runnable unit of work that runs indefinitely and retrieves a message from the queue to print the message on the Android log.

After that, we will create and start 10 threads that are going to execute the Runnable unit of work task:

public void start() {
    // Creates the Loop as a Runnable
    Runnable task = new Runnable() {
        @Override
        public void run() {
            while(true) {
                String message = pullMessage();
                Log.d(Thread.currentThread().
                         getName(),message);
		     // Do another processing
             }
         }
     };
    // Create a Group of Threads for processing
    for(int i=0; i< MAX_THREAD_COUNT; i++){
         new Thread(task).start();
    }
 }

The pullMessage(), which is a synchorized method, runs a mutual exclusion and puts the thread in the waiting state when it reaches the wait() method. All the created threads will stay in this state until another thread calls notifyAll():

// Pulls a message from the queue
// Only returns when a new message is retrieves
// from the queue.
private synchronized String pullMessage(){
    while (queue.isEmpty()) {
        try {
            wait();
        } catch (InterruptedException e) { ... }
    }
    return queue.pop();
}
// Push a new message to the tail of the queue if
// the queue has available positions
public synchronized void pushMessage(String logMsg) {
    if ( queue.size()< MAX_QUEUE_SIZE ) {
        queue.push(logMsg);      
        notifyAll();
    }
}

When any thread is in the waiting state, it releases the lock temporarily and gives a chance to another thread to enter the mutual exclusion to push new messages or enter into the wait state.

In the following snippet, we will first create the Logger instance and then we will call the start method to start the working threads and we will push 10 messages into a queue of work to be processed.

When the pushMessage()method is invoked, a new logging message is inserted at the end of the queue and notifiyAll() is invoked to notify all the available threads.

As the pullMessage() method runs in a mutual-exclusion (synchronized) zone, only one thread will wake up and return from the pull method. Once pullMessage() returns, the logging message is printed:

Logger logger =new Logger();
logger.start();
for ( int i=0; i< 10 ; i++) {
    ...
    logger.pushMessage(date+" : "+"Log Message #"+i);
}

In the following console output, we have an example of the output that this code will generate and the logging messages are processed by any available threads in an ordered manner:

D/Thread-108(23915): <Date>: Log Message #0
D/Thread-109(23915): ...: Log Message #1
D/Thread-110(23915): ...: Log Message #2
D/Thread-111(23915): ...: Log Message #3

This kind of low-level construct can also be used to control shared resources (polling) to manage background execution (parallelism) and control thread pools.

Concurrent package constructs

Other Java concurrent constructs provided by java.util.concurrent, which are also available on Android SDK are as follows:

  • Lock objects (java.util.concurrent): They implement locking behaviors with a higher level idiom.

  • Executors: These are high-level APIs to launch and manage a group of thread executions (ThreadPool, and so on).

  • Concurrent collections: These are the collections where the methods that change the collection are protected from synchronization issues.

  • Synchronizers: These are high-level constructs that coordinate and control thread execution (Semaphore, Cyclic Barrier, and so on).

  • Atomic variables (java.util.concurrent.atomic): These are classes that provide thread-safe operations on single variables. One example of it is AtomicInteger that could be used in our example to solve the correctness issue.

Some Android-specific constructs use these classes as basic building blocks to implement their concurrent behavior, although they could be used by a developer to build custom concurrent constructs to solve a specific use case.

Executor framework

The Executor framework is another framework available on java.util.concurrent that provides an interface to submit Runnable tasks, decoupling the task submission from the way the task will run:

public interface Executor {
  void execute(Runnable command);
}

Each Executor, which implements the interface that we defined earlier, can manage the asynchronous resources, such as thread creation destruction and caching, and task queueing in a variety of ways to achieve the perfect behavior to a specific use case.

The java.util.concurrent comes with a group of implementations available out of the box that cover most generic use cases, as follows:

  • Executors.newCachedThreadPool(): This is a thread poll that could grow and reuse previously created threads

  • Executors.newFixedThreadPool (nThreads): This is a thread pool with a fixed number of threads and a message queue for store work

  • Executors.newSingleThreadPool(): This is similar to newFixedThreadPool, but with only one working thread

To run a task on Executor, the developer has to invoke execute() by passing Runnable as an argument:

public class MyRunnable implements Runnable {
    public void run() {
        Log.d("Generic", "Running From Thread " +
              Thread.currentThread().getId());   
	 // Your Long Running Computation Task
    }
}
public void startWorking(){
    Executor executor = Executors.newFixedThreadPool(5);
    for ( int i=0; i < 20; i++ ) {
        executor.execute(new MyRunnable());
    }
}

In the preceding code, we created ThreadPool over the factory methods with a fixed number of five threads ready to process work.

After the ExecutorService instance creation, new Runnable tasks are posted for asynchronous processing.

When a new unit of work is submitted, a thread that is free to work is chosen to handle the task; but when all the threads are occupied, Runnable will wait in a local queue until a thread is ready to work.

 

Android primary building blocks


A typical Android application is composed of the following four main building blocks:

  • android.app.Activity

  • android.app.Service

  • android.content.BroadcastReceiver

  • android.content.ContentProvider

The Activity, Service, and BroadcastReceiver are activated explicitly or implicitly over an asynchronous message called Intent.

Each of these building blocks have their own life cycle, so they could be exposed to different concurrency issues if an asynchronous architecture is used to offload work from the main thread.

Activity concurrent issues

The Activity building block has a tight connection with a presentation layer because it's the entity that manages the UI view over a defined tree of fragments and views that display information and respond to user interactions.

Android applications are typically composed of one or more subclasses of android.app.Activity. An Activity instance has a very well-defined lifecycle that the system manages through the execution of lifecycle method callbacks, all of which are executed on the main thread.

To keep the application responsive and reactive, and the activity transition smooth, the developer should understand the nature of each Activity lifecycle callback.

The most important callbacks on the Activity lifecycle are as follows:

  • onCreate(): At this state, Activity is not visible, but it is here where all the private Activity resources (views and data) are created. The long and intensive computations should be done asynchronously in order to decrease the time when the users don't get a visual feedback during an Activity transition.

  • onStart(): This is the callback called when the UI is visible, but not able to interact on the screen. Any lag here could make the user angry as any touch event generated at this stage is going to be missed by the system.

  • onResume(): This is the callback called when Activity is going to be in the foreground and at an interactable state.

  • onPause(): This is a callback called when Activity is going to the background and is not visible. Computations should end quickly as the next Activity will not resume until this method ends.

  • onStop(): This is a callback called when Activity is no longer visible, but can be restarted.

  • onDestroy(): This is a callback called when the Activity instance is going to be destroyed in the background. All the resources and references that belong to this instance have to be released.

An Activity instance that is completed should be eligible for garbage collection, but background threads that refer to Activity or part of its view hierarchy can prevent garbage collection and create a memory leak.

Similarly, it is easy to waste CPU cycles (and battery life) by continuing to do background work when the result can never be displayed as Activity is completed.

Finally, the Android platform is free at any time to kill processes that are not the user's current focus. This means that if we have long-running operations to complete, we need some way of letting the system know not to kill our process yet.

All of this complicates the do-not-block–the-main-thread rule as we need to worry about canceling background work in a timely fashion or decoupling it from the Activity lifecycle where appropriate.

Manipulating the user interface

The other Android-specific problem lies not in what you can do with the UI thread, but in what you cannot do.

Note

You cannot manipulate the user interface from any thread other than the main thread.

This is because the user interface toolkit is not thread-safe, that is, accessing it from multiple threads may cause correctness problems. In fact, the user interface toolkit protects itself from potential problems by actively denying access to user interface components from threads other than the one that originally created these components.

If the system detects this, it will instantly notify the application by throwing CalledFromWrongThreadException.

The final challenge then lies in safely synchronizing background threads with the main thread so that the main thread can update the user interface with the results of the background work.

If the developer has access to an Activity instance, the runOnUiThread instance method can be used to update the UI from a background thread.

The method accepts a Runnable object like the one used to create an execution task for a thread:

public final void runOnUiThread (Runnable)

In the following example, we are going to use this facility to publish the result from a synonym search that was processed by a background thread.

To accomplish the goal during the OnCreate activity callback, we will set up onClickListener to run searchTask on a created thread:

// Get the Views references
Button search = (Button) findViewById(R.id.searchBut);
final EditText word = (EditText) findViewById(R.id.wordEt);

// When the User clicks on the search button 
// it searches for a synonym
search.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Runnable that Searchs for the synonym and
        // and updates the UI.
        Runnable searchTask = new Runnable() {
            @Override
            public void run() {
                // Retrieves the synonym for the word
                String result = searchSynomim(
                   word.getText().toString());
                // Runs the Runnable SetSynonymResult
                // to publish the result on the UI Thread
                runOnUiThread(new SetSynonymResult(result));
            }
        };
        // Executes the search synonym an independent thread
        Thread thread = new Thread(searchTask);
        Thread.start();
    }
});

When the user clicks on the Search button, we will create a Runnable anonymous class that searches for the word typed in R.id.wordEt EditText and starts the thread to execute Runnable.

When the search completes, we will create an instance of Runnable SetSynonymResult to publish the result back on the synonym TextView over the UI thread:

class SetSynonymResult implements Runnable {
    final String synonym;

    SetSynonymResult(String synonym){
      this.synonym = synonym;
    }
    public void run() {
      TextView tv = (TextView)findViewById(R.id.synonymTv);
      tv.setText(this.synonym);
    }
};

This technique is sometime not the most convenient one, especially when we don't have access to an Activity instance; therefore, in the following chapters, we are going to discuss simpler and cleaner techniques to update the UI from a background computing task.

Service concurrent issues

These are the Android entities that run in the background, which usually perform tasks in the name application that does not require any user interaction.

Service, by default, runs in the main thread of the application process. It does not create its own thread, so if your Service is going to do any blocking operation, such as downloading an image, play a video, or access a network API, the user should design a strategy to offload the time of the work from the main thread into another thread.

As Service could have its own concurrent strategy, it should also take into account that, like Activity, it should update the UI over the main thread, so a strategy to post back the results from the background into the main loop is imperative.

In the Android services domain, the way the service is started distinguishes the nature of Service into the following two groups:

  • Started services: This is the service that is started by startService() that can run definitively even if the component that started it was destroyed. A started service does not interact directly with the component that started it.

  • Bound services: This service exists while at least one Android component is bounded to it by calling bindService(). It provides a two-way (client-server) communication channel for communication between components.

Started services issues

When we implement a started service, any application component is able to start it when it invokes the startService(Intent) method. Once the system receives startService(Intent) and the service is not yet started, the system calls onCreate() and then onStartCommand() with the arguments encapsulated on an Intent object. If the Service already exists, only onStartCommand() is invoked.

The callbacks used by a started service are as follows:

// Called every time a component starts the Service
// The service arguments are passed over the intent
int onStartCommand(Intent intent, int flags, int startId)

// Used to initialize your Service resources
void onCreate()

// Used to release your Service resources
void onDestroy()

In the onStartCommand() callback, once a long computing task is required to handle the service request, a handover to the background threads should be explicitly implemented and coordinated in order to avoid an undesired ANR:

int onStartCommand (Intent intent, int flags, int startId){
    // Hand over the request processing to your
    // background tasks
...
}

When the service is done, and it needs to publish results to the UI, a proper technique to communicate with the main thread should be used.

Bound services issues

A bound service generally used when a strong interaction between an Android component and a service is required.

When the service runs on the same process, the interaction between the Android component (client) and the bound service (server) is always provided by a Binder class returned on onBind(). With the Binder instance on hand, the client has access to the service's public methods, so when any component invokes the bound service public methods, the component should be aware of the following:

  • When a long running operation is expected to take place during the method invocation, the invocation must occur in a separate thread

  • If the method is invoked in a separated thread, and the service wants to update the UI, the service must run the update over the main thread:

    public class MyService extends Service {
     
        // Binder given to clients
        private final IBinder mBinder = new MyBinder();
    
         public class MyBinder extends Binder {
             MyService getService() {
                 // Return this instance of MyService
                 // so clients can call public methods
                 return MyService.this;
            }
        }
        @Override
       
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    
         /** Method for clients */
        public int myPublicMethod() {
          //
        }
    ...
 

Service in a separate process


When an Android service runs on its own process, it runs in an independent process, with its own address space, making the communication with the main process UI thread harder to implement the following:

<service
  android:name="SynonymService"
  android:process=":my_synonnym_search_proc"
  android:icon="@drawable/icon"
  android:label="@string/service_name"
  >
</service>

To implement a service in a different process, we need to use an inter-process communication (IPC) technique to send messages between your application and the service.

Note

IPC is the activity of sharing data across multiple processes, usually using a well-defined communication protocol. It typically has a process that acts as the client and a process that acts as the server.

There are two technologies available on the Android SDK to implement this, as follows:

  • AIDL (Android Interface Definition Language): This allows you to define an interface over a set of primitive types. It allows you create multithreaded processing services, but it adds other levels of complexity to your implementation. This is only recommended to advanced programmers.

  • Messenger: This is a simple interface that creates a queue of work for you in the service side. This executes all the tasks sequentially on single thread managed by a Handler.

We haven't given more details about these techniques yet; however, an example of this construct is going to be presented later in a more advanced chapter where all the concepts involved are more mature.

Broadcast receiver concurrent issues

This building block is a component that subscribes to system and application events and is notified when these events occur on the system. The broadcast receivers are defined statically in the application manifest or dynamically via the Context.registerReceiver().

The broadcast received is activated though the onReceive()callback and this method runs on the main thread, blocking another Android component from running if we try to execute time-consuming tasks.

Once onReceive() finishes, the system considers the object inactive and can release the resources attached to this instance and recycle the whole object. This behavior has a tremendous impact on what we can do inside, because if we hand over some processing to a concurrent thread, the resources that belong to BroadcastReceiver might be recycled and are no longer available, or in an extreme case, the process could be killed if there were no important components running on it.

Tip

Android version 11 introduced the goAsync()method on the broadcast receiver to keep the broadcast active after returning from the onReceive() function.

Android concurrency constructs

The good news is that the Android platform provides specific constructs to address the concurrency general issues and to solve the specific problems presented by Android.

There are constructs that allow us to defer tasks to run later on the main thread, communicate easily between cooperating threads, and issue work to the managed pools of worker threads and reintegrate the results back in the main thread.

There are solutions to the constraints of the Activity lifecycle, both for medium-term operations that closely involve the user interface and longer-term work that must be completed even if the user leaves the application.

While some of these constructs were only introduced with newer releases of the Android platform, they are available through the support libraries, and with a few exceptions, the examples in this book target devices that run API level 8 (Android 2.2) and higher versions.

 

Summary


In this chapter, we took a detailed look at the available Android runtimes, Android processes, and thread models.

We then introduced the concurrent issues that we would cope with when we try to implement robust concurrent programs.

Finally, we listed the basic concurrent building blocks available on the SDK to design concurrent programs.

In the next chapter, we'll take a look at some Android-specific low-level building blocks on which the other concurrency mechanisms are built: Handler, Looper, and LooperThread.

About the Author

  • Helder Vasconcelos

    Helder Vasconcelos is a Portuguese Software Engineer based on Dublin, Ireland, with more than 10 years of experience designing and developing real-time/multithreaded Java and C++ applications for the telecommunications and aviation industries. Apart from his day-to-day job, he occupies his spare time building native Android applications for Bearstouch Software and other third-party companies.

    He graduated with a degree in Electronic and Telecommunications Engineering from the University of Aveiro in January 2006. During his career, he has worked as a Software Engineer for companies such as PT Inovação (Portugal), Airtel ATN (Dublin, Ireland) and Axway (Dublin, Ireland). You can find Hélder on LinkedIn at https://www.linkedin.com/in/heldervasc or on his website at http://hvasconcelos.github.io.

    Browse publications by this author

Latest Reviews

(2 reviews total)
Good overview of the field, but not quite as detailed as I would have liked.
Excellent
Book Title
Unlock this book and the full library for FREE
Start free trial