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() { // } ...