Paul Brower bio photo

Paul Brower

Android development for fun and profit.

Email Twitter Github Stackoverflow Codementor

In Part 1 we talked about the basics of Android threading. This time we are going to solve some a common threading issue by decoupling the state of background work from how it’s presented using an IntentService and Broadcasts.

Handling process state during an orientation change

One of the most common “gotcha’s” in Android programming is managing state when the device changes orientations. If you want a responsive app then you need to consider how an activity handles an orientation change. Handling a running process during an orientation change is one of the trickier Android tasks.

Let’s consider a common issue: persisting a progress dialog between orientation changes. Let’s say you have a wonderfully written activity that has two different views according to its orientation ( landscape / portrait ). In this activity you show a progress dialog for some action, be it saving something to disk or the network. The problem is that you want to be able to change the orientation and still show a progress dialog. In this case AsyncTask fails you. Any orientation change will kill the activity and recreate it, orphaning any AsyncTask. Any thread pool created in the activity will have the same result.

In order to overcome this we need to keep the state of the process outside the activity. That is the goal of a service. From the docs for Service

A Service is an application component representing either an application’s desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use

We’re going to use a Service to do our work and use an Activity to handle user interaction and messaging. Communication between the service and the user will be done through the LocalBroadcastManager. We’re not going to use any old service, instead we are going to use a specialized service called an IntentService. The IntentService is a subclass of service. It will call onHandleIntent for each Intent it receives using a separate worker thread that does the work in serial. This is a similar approach to how AsyncTask handles it but it’s not tied to the lifecycle of an Activity.

Let’s take a look at our IntentService:

public class ProgressService extends IntentService {

    private static final String TAG = ProgressService.class.getCanonicalName();

    public static final String START = "START";
    public static final String PROGRESS = "PROGRESS";
    public static final String FINISH = "FINISH";

    // this intent will be used by activities to listen for updates
    public static final IntentFilter LISTEN_FILTER = new IntentFilter(ProgressService.class.getCanonicalName());

    static {
        LISTEN_FILTER.addCategory(START);
        LISTEN_FILTER.addCategory(PROGRESS);
        LISTEN_FILTER.addCategory(FINISH);
    }

    public static final String VALUE = "value";

    public ProgressService() {
        super(ProgressService.class.getSimpleName());
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Intent startIntent = new Intent(ProgressService.class.getCanonicalName());
        startIntent.addCategory(START);
        LocalBroadcastManager.getInstance(getBaseContext()).sendBroadcast(startIntent);

        // simulate some blocking work with progress
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
                Intent progressIntent = new Intent(ProgressService.class.getCanonicalName());
                progressIntent.addCategory(PROGRESS);
                progressIntent.putExtra(VALUE, i);
                LocalBroadcastManager.getInstance(getBaseContext()).sendBroadcast(progressIntent);
            } catch (InterruptedException e) {
                Log.e(TAG, e.getMessage(), e);
            }
        }

        Intent finishIntent = new Intent(ProgressService.class.getCanonicalName());
        finishIntent.addCategory(FINISH);
        LocalBroadcastManager.getInstance(getBaseContext()).sendBroadcast(finishIntent);

    }
}

The service is pretty simple. It does all its work in onHandleIntent. When it starts its work it sends out a broadcast with the name of the class as its action and a category of START. Each time it has progress to report it sends out another intent with a category of PROGRESS and the value of the progress. When finished, it broadcasts FINISHED.

What’s lacking here is that there is no user interaction in this service. No dialogs are created or managed, no toasts, no UI updates. A service should be created without the consideration on how it’s state is presented to the user, that’s the job of the activity.

Present the state of the process to the user with an Activity

Let’s create an Activity that listens for updates from the service and uses a ProgressDialog in a DialogFragment to inform the user.

public class MainActivity extends Activity {

    public static final String DIALOG_TAG = "dialog";
    private static final String TAG = MainActivity.class.getCanonicalName();
    private BroadcastReceiver progressReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
            fragmentTransaction.replace(R.id.fragment_container, new MainActivityFragment());
            fragmentTransaction.commit();
        }

        progressReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                ProgressDialogFragment progressDialogFragment = null;

                // if we dont already have a dialog fragment created, create one
                if (getFragmentManager().findFragmentByTag(DIALOG_TAG) == null) {
                    progressDialogFragment = new ProgressDialogFragment();
                    progressDialogFragment.show(getFragmentManager(), DIALOG_TAG);
                } else {
                    progressDialogFragment = (ProgressDialogFragment) getFragmentManager().findFragmentByTag(DIALOG_TAG);
                }

                // update the progress on the fragment
                if (intent.getCategories().contains(ProgressService.PROGRESS)) {
                    progressDialogFragment.updateProgress(intent.getIntExtra(ProgressService.VALUE, 0));
                }

                // process is finished, dismiss dialog
                if (intent.getCategories().contains(ProgressService.FINISH)) {
                    progressDialogFragment.dismiss();
                }
            }
        };
    }

    @Override
    protected void onResume() {
        super.onResume();

        LocalBroadcastManager.getInstance(this).registerReceiver(progressReceiver, ProgressService.LISTEN_FILTER);
    }

    @Override
    protected void onPause() {
        super.onPause();

        try {
            LocalBroadcastManager.getInstance(this).unregisterReceiver(progressReceiver);
        } catch (Exception e) {
            Log.e(TAG, e.getMessage(), e);
        }

    }
}

The interesting part is in the BroadcastReceiver that starts in onResume. It starts listening for messages from the Service and responds accordingly. During an orientation change the activity will be destroyed but the Service will continue sending out messages. Once the new activity has started up it will receive PROGRESS broadcasts, start the DialogFragment and then send the DialogFragment any updates that happen. The DialogFragment will get closed when the FINISH broadcast is received.

Code for the DialogFragment is here https://github.com/browep/TutorialProgressService/blob/master/app/src/main/java/browep/github/com/progressservice/ProgressDialogFragment.java and code for the MainActivityFragment which starts the Service is here https://github.com/browep/TutorialProgressService/blob/master/app/src/main/java/browep/github/com/progressservice/MainActivityFragment.java. The rest of the code can be taken from that repo.

This pattern can be used for multiple uses; database access, file I/O, network access, intensive CPU operations. The Service can also be reused for multiple interfaces. A BroadcastReceiver could be built that puts the changes up in the NotificationBar for example. Decoupling the work ( as done in the Service ) from how it is presented ( Dialog, NotificationBar ) will make for a more responsive app.