I want to tell you about the Chronos library for Android (API level >=9), which is designed to help you perform long operations, like database or network requests.
What are we trying to fix?
It’s no secret that Android uses a lot of asynchronous jobs. Very few apps work exclusively offline, without network access. And only a tiny number don’t require any interaction with data on a device — be it a database, Preferences or a file. Yet throughout the development of the platform, we haven’t had a single ‘off the shelf’ solution for this.
What we did about it — a brief history
Let’s consider the current toolkit, in the context of the task of “completing a click using the ‘authorise’ button”. What do we have?
1. Standard thread
Pretty much everything about this code is bad. It is difficult to read, there are memory leaks and it cannot be cancelled. It also doesn’t handle screen rotation changes or any API call errors (and if they are processed, things get really ugly).
This is an improvement, but it’s still not good enough. AsyncTask gives comprehensible error processing and the ability to cancel operations. However this code still cannot properly deal with screen orientation changes at the point of making the API call: the link to the Activity which defines the class is lost.
When Google introduced Loaders, everyone thought they would become the silver bullet for asynchronous jobs, replacing the AsyncTasks that had become the go-to solution. Sadly, that didn’t happen. Today, Loaders are seldom drawn on in commercial projects because they are so awkward to implement. I won’t cite example code in this section, as I did with the other two. Instead, I urge the curious reader to take a look at the official guide on this stack to appreciate the volume of code Loaders require:developer.android.com/reference/android/content/AsyncTaskLoader.html
Services are good for executing long-running tasks that ‘hang’ in the background for extended periods of app use. However services have a less than ideal structure for app launches, which require an immediate result. The biggest constraint is the requirement to transfer data through Intents, which can handle a limited amount of information and which require the transferred data to have some form of iterability. The popular Robospicelibrary uses this stack.
What does Chronos offer?
Chronos does all the work on executing a task in a parallel thread and delivers the result or execution error to the main thread. Basically, this library is a container for any kind of long-running operations. We have a full wiki on the project: some of the code is used in this article, but check outgithub.com/RedMadRobot/Chronos/wiki for full guidance.
Let’s work through a standard task using Chronos: an Activity needs to request a particular object from a specific location and accessing it takes too long to handle the operation in the UI thread. Let’s write the code first and then see what we get.
- First of all, we need to add Chronos to the project. To do this, build a dependency in gradle:
2. Let’s now describe the Activity. The ChronosActivity base class is one of the library components but you can write your own using the examples in the documentation. You can use the same Chronos code for Fragments as well.
3. All that’s left is to describe the business logic for receiving the data in the MyOperation class:
That’s it! Let’s take a closer look at what happens in the code, starting from the beginning.
Setting up the UI class
To use Chronos, you can either extend the Activity or Fragment base class from the Chronos library or include specific code in the lifecycle methods, examples of which can be found in the documentation.
The base method of the ChronosActivity class is called, where the newly created operation is transferred. Chronos then pulls the operation into a queue and starts to execute it in a parallel thread in the background.
Handling operation results
This method will be called after the operation is executed or if an exception occurs during execution. These handlers must have the signature public void onOperationFinished(ResultType). Notably, the method will only be called between onResume() and onPause() callback methods, which means you can fiddle around with the UI without worrying that it will become invalid by that point. What’s more, if the Activity is reloaded because of a screen rotation, a transition into the background for some other reason, Chronos will still return a result (the only exception is if the system runs out of memory — to prevent OutOfMemory, Chronos can erase old results).
Where is the call coming from?
You may have noticed that the Activity does not invoke any specific interfaces. So where exactly does this method come from? The answer is the reflection in the code. This decision was driven by TypeErasure in Java, which does not allow simultaneous execution of the same template interface with different parameters. So we used this approach to allow a single Activity to process the results of an infinite number of operations.
Setting up operation classes
The ChronosOperation class encapsulates the business logic for receiving a particular type of object — BusinessObject in this case. All user operations should be extended from ChronosOperation.
This abstract method of the ChronosOperation class deals with the business logic for receiving an object. It is executed in a parallel thread, which allows running long operations without affecting the app interface. What’s more, any exceptions that are generated will be passed back to the calling object, without causing the app to crash.
Naming the result
The next method and class enable defining a results handler for each specific operation in the Activity code, using class as the parameter type for theonOperationFinished method. You can use the same results class for different operations if you want the results to be processed in the same way.
To sum up, the minimum set of code areas required to work with Chronos comprises:
- Operation class
- UI object code to call the operation
- UI object code to process the results
What else is there?
- To prevent the loss of a call after a screen rotation, Chronos allows you to “name” operation launches;
- You can cancel operations;
- If you need the operation results to be returned not just to the object that made the call, but to all those subscribed to it, you can create Broadcast launches;
- What’s more, you can execute operations synchronously using the Chronos class.
So where and why should you use Chronos?
- Chronos looks after data transfer between threads, which means you only need to worry about the business logic;
- Chronos fits with the nuance of Activity and Fragment lifecycles, returning results only when they are ready to be processed and storing the data until then
- Chronos does not create memory leaks. You are not at risk of crashing the app because too many Activity objects have leaked
- Chronos is covered with unit tests
- Finally, Chronos is an open-source project. You can always take the code and tweak it to suit your needs. The tests allow you to easily validate code changes
Check out the project on GitHub. This contains the full documentation on the library, example use cases and, of course, the original code.