Everything you need to know about Android Threading

Every Android developer eventually encounters the need to manage threads within their applications.

Upon launching, an Android application initiates its first execution thread, referred to as the “main” thread. This main thread is responsible for directing events to the appropriate user interface elements and interacting with components from the Android UI toolkit.

To maintain a responsive application, it is crucial to avoid burdening the main thread with operations that could potentially block it.

Network requests, database interactions, and loading specific components are prime examples of operations that should be offloaded from the main thread. Executing these operations on the main thread leads to synchronous execution, rendering the UI unresponsive until their completion. Therefore, these operations are typically delegated to separate threads, preventing UI blockage during their execution (i.e., they run asynchronously from the UI).

Android offers numerous methods for thread creation and management, and many third-party libraries simplify this process. However, with this abundance of approaches, selecting the most appropriate one can be perplexing.

This article will guide you through common Android development scenarios where threading is crucial and present straightforward solutions applicable to these and other situations.

Threading in Android

In Android, threading components can be broadly classified into two categories:

  1. Threads tethered to an activity/fragment: These threads are bound to the lifecycle of the activity/fragment, terminating upon its destruction.
  2. Threads independent of any activity/fragment: These threads persist beyond the lifespan of the activity/fragment that spawned them.

Threading Components Tied to an Activity/Fragment

AsyncTask

AsyncTask is the most fundamental Android threading component. Its simplicity makes it suitable for basic scenarios.

Sample usage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ExampleActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        new MyTask().execute(url);
    }

    private class MyTask extends AsyncTask<String, Void, String> {

        @Override
        protected String doInBackground(String... params) {
            String url = params[0];
            return doSomeWork(url);
        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            // do something with result 
        }
    }
}

However, AsyncTask falls short if your deferred task needs to outlive the activity/fragment. Even a simple screen rotation can trigger activity destruction.

Loaders

Loaders address the limitation mentioned above. They halt automatically upon activity destruction and restart when the activity is recreated.

Two primary types of loaders exist: AsyncTaskLoader and CursorLoader. We’ll delve into CursorLoader later in this article.

AsyncTaskLoader resembles AsyncTask but with added complexity.

Sample usage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class ExampleActivity extends Activity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getLoaderManager().initLoader(1, null, new MyLoaderCallbacks());
    }
    
    private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks {

        @Override
        public Loader onCreateLoader(int id, Bundle args) {
            return new MyLoader(ExampleActivity.this);
        }

        @Override
        public void onLoadFinished(Loader loader, Object data) {

        }

        @Override
        public void onLoaderReset(Loader loader) {

        }
    }

    private class MyLoader extends AsyncTaskLoader {

        public MyLoader(Context context) {
            super(context);
        }

        @Override
        public Object loadInBackground() {
            return someWorkToDo();
        }
        
    }
}

Threading Components Independent of Activities/Fragments

Service

Service proves useful for executing prolonged (or potentially lengthy) operations without a UI.

Service runs on the main thread of its hosting process; it doesn’t spawn its own thread or run in a separate process unless explicitly specified.

Sample usage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class ExampleService extends Service {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        doSomeLongProccesingWork();
        stopSelf();

        return START_NOT_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

With Service, the onus of stopping it after task completion falls on you, by invoking either stopSelf() or stopService().

IntentService

Similar to Service, IntentService operates on a separate thread and automatically terminates upon finishing its work.

IntentService typically handles short, UI-independent tasks.

Sample usage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class ExampleService extends IntentService {
    
    public ExampleService() {
        super("ExampleService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        doSomeShortWork();
    }
}

Seven Threading Patterns in Android

Use Case No. 1: Sending a Network Request Without Server Response

Situations arise where you need to send an API request without requiring a response. For instance, sending a push registration token to your application’s backend.

Network communication necessitates offloading this task from the main thread.

Option 1: AsyncTask or Loaders

While AsyncTask or loaders can handle the call, their dependence on the activity lifecycle poses a challenge. You either wait for call execution and try to prevent the user from leaving the activity or hope for execution before activity destruction.

Option 2: Service

Service appears more suitable as it’s not bound to any activity, enabling it to continue the network call even after activity destruction. The lack of a required server response further strengthens its case.

However, since services run on the UI thread by default, manual thread management is necessary. Ensuring service stoppage post-call adds to the complexity, making it more effort than warranted for such a simple task.

Option 3: IntentService

This emerges as the optimal choice. Its independence from activities, execution on a non-UI thread, and automatic termination perfectly align with our requirements.

Use Case No. 2: Making a Network Call and Handling the Server Response

This use case is more prevalent, such as invoking a backend API to populate screen fields with the response data.

Option 1: Service or IntentService

While effective in the previous case, using them here is inadvisable. Extracting data from them into the main UI thread introduces significant complexity.

Option 2: AsyncTask or Loaders

These appear as the intuitive solution – straightforward and easy to use.

However, they introduce boilerplate code and make error handling cumbersome. Even simple networking calls necessitate exception handling, requiring response wrapping in custom classes with error information and success flags.

This overhead for every call is addressed by a superior solution: RxJava.

Option 3: RxJava

RxJava is a library developed by Netflix. It’s practically magic in Java.

RxAndroid](https://github.com/ReactiveX/RxAndroid) brings RxJava to Android, simplifying asynchronous task management. Learn more about RxJava on Android here.

RxJava introduces two key components: Observer and Subscriber.

An observer encapsulates an action. It executes the action, returning the result upon success or an error upon failure.

A subscriber, on the other hand, receives the result (or error) from an observable by subscribing to it.

With RxJava, you first define an observable:

1
2
3
4
Observable.create((ObservableOnSubscribe<Data>) e -> {
    Data data = mRestApi.getData();
    e.onNext(data);
})

Once the observable is set up, you subscribe to it.

RxAndroid grants you control over the threads for both action execution within the observable and response reception (result or error).

This is achieved by chaining the observable with these two functions:

1
2
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()

Schedulers dictate the thread on which actions execute. AndroidSchedulers.mainThread() represents the scheduler associated with the main thread.

Assuming your API call is mRestApi.getData() and returns a Data object, a basic call would resemble this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Observable.create((ObservableOnSubscribe<Data>) e -> {
            try { 
                        Data data = mRestApi.getData();
                        e.onNext(data);
            } catch (Exception ex) {
                        e.onError(ex);
            }
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(match -> Log.i(rest api, "success"),
            throwable -> Log.e(rest api, "error: %s" + throwable.getMessage()));

Even without delving into its other advantages, RxJava’s ability to abstract away threading complexity allows for more robust code.

Use Case No. 3: Chaining Network Calls

Sequential network calls, where each operation depends on the previous one’s output, demand caution to avoid spaghetti code.

Consider fetching a token through one API call and using it in a subsequent call.

Option 1: AsyncTask or Loaders

This approach almost inevitably leads to spaghetti code, making the logic convoluted and requiring excessive redundant code throughout your project.

Option 2: RxJava using flatMap

RxJava’s flatMap operator takes an emitted value from the source observable and generates another observable. This enables observable chaining, where each observable leverages the previous one’s output.

Step 1. Define the observable to fetch the token:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public Observable<String> getTokenObservable() {
    return Observable.create(subscriber -> {
        try {
            String token = mRestApi.getToken();
            subscriber.onNext(token);

        } catch (IOException e) {
            subscriber.onError(e);
        }
    });
}

Step 2. Create the observable to retrieve data using the token:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public Observable<String> getDataObservable(String token) {
    return Observable.create(subscriber -> {
        try {
            Data data = mRestApi.getData(token);
            subscriber.onNext(data);

        } catch (IOException e) {
            subscriber.onError(e);
        }
    });
}

Step 3. Chain the observables and subscribe:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
getTokenObservable()
.flatMap(new Function<String, Observable<Data>>() {
    @Override
    public Observable<Data> apply(String token) throws Exception {
        return getDataObservable(token);
    }
})
.subscribe(data -> {
    doSomethingWithData(data)
}, error -> handleError(e));

This approach extends beyond network calls; it applies to any set of actions requiring sequential execution on separate threads.

All the above use cases are relatively simple, involving thread switching after task completion. This approach also accommodates more advanced scenarios requiring active inter-thread communication.

Use Case No. 4: Communicating with the UI Thread from Another Thread

Imagine uploading a file and updating the UI upon completion.

Since file uploads can be time-consuming, a background service, preferably IntentService, is suitable.

The challenge lies in invoking a UI thread method after the background file upload finishes.

Option 1: RxJava Inside the Service

This approach is not ideal. Callbacks are needed when subscribing to the Observable, and IntentService is designed for synchronous calls, not callbacks.

Using a Service necessitates manual service stoppage, increasing complexity.

Option 2: BroadcastReceiver

Android provides this component for listening to global and custom events. You can create a custom event triggered upon upload completion.

This involves creating a custom BroadcastReceiver subclass, registering it in the manifest, and using Intent and IntentFilter to define the custom event. The sendBroadcast method triggers the event.

Manifest:

1
2
3
4
5
6
7
8
<receiver android:name="UploadReceiver">
   
    <intent-filter>
        <action android:name="com.example.upload">
        </action>
    </intent-filter>
   
</receiver>

Receiver:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class UploadReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getBoolean(success, false) {
            Activity activity = (Activity)context;
            activity.updateUI();
    }

}

Sender:

1
2
3
Intent intent = new Intent();
intent.setAction("com.example.upload"); 
sendBroadcast(intent);

While viable, this approach demands effort, and excessive broadcasts can hinder performance.

Option 3: Using Handler

A Handler attaches to a thread and enables action execution on that thread via messages or Runnable tasks. It collaborates with Looper, responsible for message processing within a thread.

When creating a Handler, you can specify the associated thread’s Looper via the constructor. To target the main thread, use Looper.getMainLooper().

To update the UI from a background thread, create a handler attached to the UI thread and post an action as a Runnable:

1
2
3
4
5
6
7
Handler handler = new Handler(Looper.getMainLooper());
    handler.post(new Runnable() {
        @Override
        public void run() {
            // update the ui from here                
        }
    });

This surpasses the first option, but an even simpler method exists…

Option 3: Using EventBus

EventBus, a popular library by GreenRobot, facilitates safe inter-component communication. Given our need for UI updates, it’s the simplest and safest choice.

Step 1. Define an event class (e.g., UIEvent).

Step 2. Subscribe to the event:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Subscribe(threadMode = ThreadMode.MAIN)  
public void onUIEvent(UIEvent event) {/* Do something */};

 register and unregister eventbus : 

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    super.onStop();
    EventBus.getDefault().unregister(this);
}

Step 3. Post the event: EventBus.getDefault().post(new UIEvent());

The ThreadMode annotation parameter specifies the subscription thread. Here, we choose the main thread to enable UI updates upon event reception.

You can augment the UIEvent class with additional information as needed.

In the service:

1
2
3
4
5
6
class UploadFileService extends IntentService {
    // …
    Boolean success = uploadFile(File file);
    EventBus.getDefault().post(new UIEvent(success));
    // ...
}

In the activity/fragment:

1
2
@Subscribe(threadMode = ThreadMode.MAIN)  
public void onUIEvent(UIEvent event) {//show message according to the action success};

EventBus significantly streamlines inter-thread communication.

Use Case No. 5: Two-Way Communication Between Threads Based on User Interactions

Consider a media player that continues playback even when the application is in the background. This requires UI interaction with the media thread (play, pause) and media thread updates to the UI (errors, buffering status).

A comprehensive media player example is outside this article’s scope. You can find excellent tutorials here and here.

Option 1: Using EventBus

While possible, using EventBus here is generally unsafe. Posting events from the UI thread to a service lacks certainty about the service’s running state.

Option 2: Using BoundService

A BoundService is directly connected to an activity/fragment, ensuring the activity/fragment is aware of the service’s status and has access to its public methods.

Implementation involves creating a custom Binder within the service and a method that returns the service instance:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class MediaService extends Service {

    private final IBinder mBinder = new MediaBinder();

    public class MediaBinder extends Binder {
        MediaService getService() {
            // Return this instance of LocalService so clients can call public methods
            return MediaService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

}

Binding the activity to the service requires implementing ServiceConnection, which monitors the service status, and using the bindService method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// in the activity
MediaService mService;
// flag indicates the bound status
boolean mBound;

@Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, MediaService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {

            MediaBinder binder = (MediaBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };

A complete example can be found here.

User interactions like Play/Pause are handled by binding to the service and invoking the appropriate public method.

Communicating media events back to the activity/fragment can be achieved using previously discussed techniques (BroadcastReceiver, Handler, or EventBus).

Use Case No. 6: Parallel Action Execution with Result Retrieval

Imagine building a tourist app that displays attractions from various data providers on a map. Since providers can be unreliable, you aim to handle individual failures gracefully.

Parallel execution necessitates running each API call on a separate thread.

Option 1: Using RxJava

RxJava’s merge() or concat() operators combine multiple observables. Subscribing to the “merged” observable provides all results.

However, a single API call failure results in an overall failure.

Option 2: Using Native Java Components

Java’s ExecutorService creates a configurable pool of threads for concurrent task execution. It returns a Future object that eventually yields all results via the invokeAll() method.

Tasks submitted to the ExecutorService are encapsulated within the Callable interface, allowing for exception handling.

Upon receiving results from invokeAll(), you can individually inspect and process them.

For example, fetching three attraction types from different endpoints in parallel:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ExecutorService pool = Executors.newFixedThreadPool(3);
    List<Callable<Object>> tasks = new ArrayList<>();
    tasks.add(new Callable<Object>() {
        @Override
        public Integer call() throws Exception {
            return mRest.getAttractionType1();
        }
    });

    // ...

    try {
        List<Future<Object>> results = pool.invokeAll(tasks);
        for (Future result : results) {
        try {
            Object response = result.get();
            if (response instance of AttractionType1... {}
            if (response instance of AttractionType2... {}
                ...
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

This enables error checking for each action independently, allowing you to ignore specific failures.

This approach surpasses RxJava in simplicity, conciseness, and resilience to single-point failures.

Use Case #7: Querying a Local SQLite Database

Due to their potential for long execution times, database operations are best performed on a background thread to prevent UI freezes.

Querying an SQLite database returns a Cursor for data retrieval.

1
2
Cursor cursor = getData();
String name = cursor.getString(<colum_number>);

Option 1: Using RxJava

Similar to fetching backend data, RxJava can be employed for database interactions:

1
2
3
4
5
6
public Observable<Cursor> getLocalDataObservable() {
    return Observable.create(subscriber -> {
        Cursor cursor = mDbHandler.getData();
        subscriber.onNext(cursor);
    });
}

The observable from getLocalDataObservable() can be used as follows:

1
2
3
4
5
getLocalDataObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
 .subscribe(cursor -> String name = cursor.getString(0),
                   throwable -> Log.e(db, "error: %s" + throwable.getMessage()));

While effective, a more specialized solution exists for this scenario.

Option 2: Using CursorLoader + ContentProvider

Android provides CursorLoader, a native component for loading SQLite data and managing the associated thread. This Loader returns a Cursor for data access using methods like getString(), getLong(), etc.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class SimpleCursorLoader extends FragmentActivity implements
LoaderManager.LoaderCallbacks<Cursor> {

  public static final String TAG = SimpleCursorLoader.class.getSimpleName();
  private static final int LOADER_ID = 0x01;
  private TextView textView;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_cursor_loader);
    textView = (TextView) findViewById(R.id.text_view);
    getSupportLoaderManager().initLoader(LOADER_ID, null, this);

  }

  public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {

    return new CursorLoader(this,
      Uri.parse("content://com.github.browep.cursorloader.data")
      , new String[]{"col1"}, null, null, null);
    }

    public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
      if (cursor != null && cursor.moveToFirst()) {
        String text =  textView.getText().toString();
        while (cursor.moveToNext()) {
          text += "<br />" + cursor.getString(1);
          cursor.moveToNext();
        }
        textView.setText(Html.fromHtml(text) );
      }
    }

    public void onLoaderReset(Loader<Cursor> cursorLoader) {
      
    }

}

CursorLoader integrates with ContentProvider, a component offering real-time database features (change notifications, triggers) for enhanced user experiences.

No One-Size-Fits-All Solution for Android Threading

Android offers various threading mechanisms, each with strengths and weaknesses.

Selecting the appropriate approach based on your use case is crucial for maintainability and ease of implementation. Native components excel in some situations, while third-party solutions shine in others.

I hope this article proves valuable in your future Android endeavors. Feel free to share your threading experiences and any use cases where these solutions thrive or fall short in the comments below.

Licensed under CC BY-NC-SA 4.0