In Android, you shouldn’t do anything that blocks the main thread. But what does that really mean? At a talk presented by the Bay Area Android Dev Group, Ari Lacenski considers the things you need to bear in mind when programming for long-running, potentially complicated tasks in Android applications. We look at the requirements of a real-world application, discuss AsyncTask
, Activity
, and Service
, and build a more maintainable solution.
Android Threads (0:46)
When we talk about threading, we know that an Android application has at least one main thread. This thread is created at the same time that the Application
class is created for your Android app. The responsibilities of the main thread are mainly aimed to draw the UI: it is intended to handle user interaction, to draw the pixels in the screen, and to launch Activities. Any code that you add to an Activity
runs with resources that are left over after the UI thread is finished, so the app stays responsive to the user.
You might think that it’s no big deal to add some extra code to your Activity
. For example, you could have a list view, build up a list, and add a few items to it. You might sort the list and pass it to a list adapter. With the resources that are free, the processor will try to draw the list for you.
However, some code that you add to an Activity
can run for so long that it is then not actually allowed by the platform to run on the UI thread. A fairly common example of this is trying to make a network call in your application. You could try to get an instance of default URLConnection
and try to run that network call right there in the Activity
. This would compile just fine, but at run time, you would get a NetworkOnMainThreadException
. For the most part, this is an extreme example. The platform lets you do far more than you should be doing on the UI thread.
For a less extreme example, let’s return to the list view idea. Let’s say that instead of populating a static list, you want to query your local database and insert the results of that query into your list. I’m told that if you use Realm, it is fine to do that query on the UI thread. However, let’s assume that you are using a database backed by SQLite. A lot of code is needed to make the query, access the database engine, receive the results, sort them, and put them in your ArrayAdapter
. If all of this running in the Activity
, your UI interaction can be really be bogged down.
You might be thinking that once, you read from shared preferences on the UI thread, and nothing bad happened. How bad could it be? Well, the problem compounds itself. As you do more and more work that interrupts or shares time with the UI thread, the more the application will tend to skip and lag, and animations will fail to update properly. The user might wonder what is wrong with the app, and the worst case happens if the user is made to wait so long that they get an error message that says, “Application not responding. Would you like to wait or kill the application?” That is the equivalent of you, the developer, suddenly walking around with a giant “Kick Me” sign taped to your back. The user is left to wonder if the app is trustworthy or just a resource hog.
”Each thread allocates a private memory area that is mainly used to store method local variables and parameters during the execution of [the thread]. The private memory area is allocated when the thread is created and deallocated once the thread terminates.”
- Anders Göransson, Efficient Android Threading
I really enjoyed this quote from the book Efficient Android Threading that talks about what threads are and what they do. It helps me understand what a background thread is, and how it is an alternative to running in the main thread. This quote also calls attention to the fact that you are responsible for the life cycle of any alternative threads that you create. Android has a lot of architectural options to help you with this. But you should keep in mind that if you’re not working with the UI thread, then at some level it is your responsibility to know what threads in your application are active.
The last point I want to bring up about multi-threading is that there is a small time delay each time the execution jumps from one thread to another. This is called context switching, and we’ll start seeing it more as we get into chaining and developing a more complex series of tasks.
AsyncTask
(5:51)
Now, I want to talk about a couple of the tools that the Android SDK gives you to deal with the burden of having to manage your own threads. AsyncTask
is a definite go-to. It makes it really convenient to be in an Activity
and to test if you can run an operation asynchronously. By creating an instance of AsyncTask
, you can run that operation right there in the Activity
. You can do this, but we’ll talk later about whether you should.
The first of two important methods that you’ll see with AsyncTask is doInBackground
, which is the method that you override to say what you want to do in the background thread. The second method is onPostExecute
, which runs again in the UI thread. You have the opportunity to give it a result from the population you did in doInBackground
. To tell you the roundtrip of conditions, you can do something in the background, pass it onPostExecute
, and move on with execution.
What is AsyncTask
really doing? To see code from AsyncTask
, just ctrl-click on the class name. I think it’s really worthwhile to actually read AsyncTask
code, because it tells you a lot about what the Android thread model is doing.
// AsyncTask.java
private static class InternalHandler extends Handler {
@Override
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
The most interesting part of this class is the fact that there’s an instance of a handler. You may not have seen handlers before, because you really never see them in UI code. The job of a handler is to provide an alternative to writing a series of methods and expecting them to all call one another to complete a series of tasks. Instead, the idea with the handler is to have little bits of code that are referenced from the switch
statement defined in the handleMessage
method. When you send a message to that handler object, you tell it to do that part.
AsyncTask
starts a thread when the execute
method is called. You don’t have to interact with that. Then, everything you wrote in doInBackground
executes. Unbeknownst to you, that handler is sent the message MESSAGE_POST_RESULT
. That is the signal to AsyncTask
to move on to the onPostExecute
method. That sounds very convenient, but the caveat to keep in mind while using this technique is that it is meant for very simple operations that will not get more complex over time.
Let’s suppose that the situation that I just described is not actually adequate for the needs that you have. But let’s say that you have two tasks that need to run sequentially, and both of them are implemented asynchronously. Because you are already running a background thread with AsyncTask
, you might start the first task and then in doInBackground
, call the second task. This is not allowed by the Android thread model. So, potentially what you could do is queue the second task at the time that the first task finishes. You see this very often in callback chaining in JavaScript. But while that is possible, here’s what it looks like.
new AsyncTask<Void, Void, Boolean>() {
protected Boolean doInBackground(Void... params) {
doOneThing();
return null;
}
protected void onPostExecute(Boolean result) {
AsyncTask doAnotherThing =
new AsyncTask<Void, Void, Boolean>() {
protected Boolean doInBackground(Void... params) {
doYetAnotherThing();
return null;
}
private void doYetAnotherThing() {}
};
doAnotherThing.execute();
}
private void doOneThing() {}
}.execute();
If you’re still puzzled and don’t find this code very easy to read, you’re right. It’s terrible, which is not helped by the fact that this code is messy and inside out. The reason that it’s terrible is because here, you’re starting a new thread and requiring the AsyncTask
executer to go through the entire thread lifecycle for your first task. You call doOneThing
and it does one thing, and then it returns to the UI thread. For just that moment, you are again blocking the UI thread for no other reason than starting another background task. You are interleaving the responsibilities of the UI and the thread pool executer, but there isn’t actually a good reason to do that. As I mentioned earlier, you are actually incurring a computational delay every time you do that. And, in addition to the fact that this is an unmaintainable piece of code, we have a fairly strong design reason to not do this.
In this situation, where we have a task that evolves in complexity, there are some other things that we can look at. The Android SDK provides a decently rich array of options so that, for the most part, you don’t have to deal with starting and managing your own threads.
IntentService
(11:57)
If you thought that I was warming up to the topic of Services, you’re right. I used an IntentService
to implement the following use case. With IntentService
, another thread runs and maintains the Android environment for you. It can run completely independently of the UI thread, and it can even run while the application is not foregrounded. The special benefit of using IntentService
is that you don’t have to remember to start or shut down that thread.
All services are started from an activity. They are heavier-duty and harder to work with than AsyncTask
, so it just requires more set up and a Manifest
change. But, one benefit of starting an Intent
is that you can still pass data in through the Intent Bundle
. We will use that fact to address how to communicate between our Activity
and the task that we’re trying to do in the background.
Signaling Between Activity and Service (13:29)
The best go-to for learning how to do this in a real application is the CodePath guides. I will summarize it now because we’ll be returning to this later. So, what is happening is that the Activity
is starting the Service
. The Activity
is also interested in being able to communicate with the Service
, so that we can know when the Service
is done. We can then show feedback, and create the object ResultReceiver
. We can then pass that ResultReceiver
into the Service
to prepare it to be started through the Intent Bundle
. Now, the Service
has a reference to an object that will run UI code. The Service
does what it’s supposed to do. In the very last instruction, we call rr.send
before onHandleIntent
finishes and terminates the thread. By doing so, we can send a RESULT
code and a bundle of data. So, when you call rr.send
, your Activity
hears about it.
// Activity
ResultReceiver rr = new
ResultReceiver() {
@Override
onReceiveResult() {
// do stuff!
}
}
// add rr to extras
startService(myServiceIntent)
// Service
onHandleIntent(Intent i) {
ResultReceiver rr =
i.getExtras().get(RR_KEY)
(assigned from bundle)
rr.send(Activity.RESULT_OK
successData);
}
Designing Task Communication (14:57)
At this point, we have built up what is becoming more and more of a complex task. I want to describe a piece of work that I did at Mango Health, where I was trying to write a login solution. In our app, it’s possible to use the app without being logged in. Then, we offer users the ability to login so that they may sync their local database with their remote database. Because we wanted to support syncing data with a remote, the login process was fairly heavy. So, to help me design the login process, I boiled it down five principles.
- The first thing we care about is having the whole task run on a background thread. We don’t want to do this in the UI. The UI’s responsibility is only to show the loading spinner and inform the user when they have logged in.
- The task should be able to signal whether the login succeeded or failed.
- We know that this will be complicated, because there are multiple tasks before we’ll have to run and complete in a certain order.
- Some of those tasks will be asynchronous, like the database sync. That step should run not independently of everything else, but as a step along the way.
- Lastly, we should be able to read the code when we finish writing it.
Building a Login Task (16:45)
Building a login task required a lot of steps, and it was daunting at first to figure out what order to have tasks run in. It helped me to break them down by the intended feedback and by the synchronous or asynchronous pattern that they follow. I highlighted the following steps because I knew that those processes were necessarily asynchronous.
- Obtain an auth token with a network request
- Fetch user account with a network request
- Sync (push) remote DB with new local DB
- Sync (push) the new database
These four tasks need to fit within the flow process. We also have a few other tasks that we can run synchronously given that we’re not blocking the UI thread, design principle number one. So, I also tried to add in natural divisions where it was possible to separate tasks and break down the work into methods. I didn’t put in divisions where either a task was a direct result of another, or if one part produced data that was critical for the next. Lastly, I also thought about how it would be important for certain tasks to signal back to the Activity
. Either one task failed so badly at the start of a process that the user could not continue, or we completed all steps and could display the login spinner.
The entire list of tasks boils down to two main principles. The first is, try to find ways that you can break down your potentially complicated task into smaller tasks. The second principle is to try to come up with ways to communicate both task failure and task success back to the Activity
.
Setting Up a Logged-in State (19:03)
This is just one example of what I mean by callback chaining, where you run one set of operations after another because they are fairly independent of one another. But in a situation where you have multiple asynchronous tasks directly following one to another, you have to start with the first. In our case, you start with getting a login token from the server before anything else. If that fails, then we exit immediately. But, if it succeeds, then we can proceed to the next asynchronous task. And so, you end up with this chain of jobs that all need to run in order but asynchronously.
Let’s take a look at one way that I considered solving this problem. I tried to implement this entire thing as something that could be started from the Service
, but would run in a module format. I have a class full of methods, and as we scroll, we just see more methods that all do different things. This works, but is one gnarly, big, long piece of code. My issue with writing the class this way was that it was really, really hard to remember how all the parts of code worked together. When do I call this method? Why do I call that method?
If I was given this code by a previous developer, I would not have had any idea what this class is supposed to do. There is a login manager, but I have no idea at what point login is actually successful. The reason for that is that in the callbacks to these asynchronous processes, there is another method call to some other method in this class.
Messaging Handler (21:58)
This code is organized, but only kind of. It works okay, but isn’t a great solution. I want to talk about ways we can get back to a better solution. Fortunately, we aren’t required to write a class that way; we have other options. Earlier, we’ve seen a handler being used to help AsyncTask
run. It turns out, using handlers is also appropriate for this same kind of situation. What we need to do to implement this work with a handler is, again, subdivide it into pieces. We create an implementation for handleMessage
that addresses each of the situations that we are trying to get through. When one piece of code finishes, it takes a reference to the handler that it knows about in that class, and it sends a message telling it to run the next piece of code.
// MyTaskModule.java
private static class LoginHandler extends Handler {
MyCallback callback;
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case QUIT:
callback.onFailure();
break;
case STARTED:
doStuff();
send(obtainMessage(NEXT_STEP));
break;
case NEXT_STEP:
callback.onSuccess();
break;
}
}
}
To start, we send the handler a started message. After it runs that code, it calls NEXT_STEP
to take us to that case. We also give ourselves the opportunity to bail by being able to send a message called QUIT
. That would allow us to escape from the handler and call some code that shuts down the entire thing.
To show you how this works, I have a MangoLoginHandler
. This is actually an instance, or subclass, of the handler itself. All of our work is being done inside handleMessage
. Our code could be split up further into additional methods if we wanted. At the bottom of my code, I have a module with a public method that lets us start running the code from the Service
by sending the message LOOPER_STARTED
. We take in all the data that I need to make this process work. When we get a reference to the handler and call post, the handler is started on whatever thread we happen to be on. Because I’m intending to start this code from an IntentService
, I am not on the main thread. All of this is running, encapsulated within a background thread.
As much as that looked like a giant wall of code, I think that it could still be split better into individual methods. But just from reading the tags in my handler, I felt like I had a much better sense of what that code was actually proposing to do.
Activity to Service to Module (25:57)
There is another piece of this puzzle that I have not spoken about yet. We can communicate between an Activity
and a Service
. We know that we can create this task module, put a handler inside of it, and start that module from the Service
. But the part that’s missing right now is being able to communicate from the handler, which is doing all the actual work, back into the Service
and the Activity
. This is necessarily so that we can signal either failure or success.
What you can do is define a very simple interface in the task module. This interface can be internal, but public. You can make an instance of that interface, a requirement or required argument to whatever method you choose to kick off your handler operation. Lastly, you can get the Service
that started the handler or started the task module to implement that callback interface. What you end up with is a Service that has an onSuccess
method and an onFailure
method. These methods are only meant to be consumed by the task module that is running on that Service.
// MyTaskModule
public interface MyCallback() {
public void onSuccess();
public void onFailure();
}
// rest of task implementation
public void start(MyCallback callback) {
// call onSuccess or
// onFailure here!
}
From the Activity
, you start your Service
and pass in a ResultReceiver
in the bundle. In the Service
, you first implement the callback interface so that the Service
is an instance of that kind of object. When you start up your task module, you pass in the Service
so that it becomes available to the module. In the module, your handler is runs through all the steps, and will ideally call onSuccess
. At the time that it calls onSuccess
, it uses the ResultReceiver
from the Activity
so that you are back in the Service
. onSuccess
fires, so you can send an OK result back to the Activity
. The user no longer sees a loading spinner and is logged in.
Summary (28:10)
We’ve created, essentially, four objects. You have your Activity
, which is managing control from the user’s point of view. You have a Service
that you have set up to take care of all these tasks for you. It has a line of communication to the Activity
. You have a separate module created to pull some of this code out of the Service
to make it more reusable. Within the task module, you have a handler that is able to go through the entire procedure, and communicate what you need to know all the way back to the Activity
so that the user can respond.
Further Reading (28:55)
As I was studying this subject, I found these resources to be really useful. Again, there are the CodePath guides. Efficient Android Threading is a book that I’ve been really enjoying. Lastly, the Android docs on processes and threads, as well as multiple threads have been helpful.
Q&A (29:30)
Q: Can I code something similar but using the Android Account Manager?
Ari: I’m glad you brought that up, because it wasn’t that clear from the code that I showed. We are also actually using the Google Account Manager. Registering a user account on the device, like a Google account, is actually one of the many tasks that this code does. This is not actually meant to replace the use of the account manager, but you can make writing the account data into the account manager system part of this whole process.
Q: I saw a Google presentation that suggested using a Sync Adapter to queue things up and handle everything responsibly. Is that something to consider?
Ari: It’s reasonable. I think that when using Sync Adapter, you have to build an implementation that fits with the other tasks that you need to do. If it’s the case that you don’t actually need to perform other tasks, then there’s no good reason to use a network library to do what Sync Adapter does. If a call to Sync Adapter will do what you need, like syncing a Google account or device to remote, you could make the architectural decision yourself of whether to make a direct call or have a wrapper. For something like that, I would probably give it a really thin wrapper. For syncing Google account data from local to remote, that is definitely the most appropriate thing to choose.
Stephan: One more thing about the Sync Adapter - there is a lot of boilerplate code. You have to create your own content provider. But, it is supposed to save you some battery. It’s a lot of work, but it is supposed to be better.
Q: Would you go into a little more detail about signaling back into the UI thread? In particular, do you have to lock? How do your callbacks end up updating the actual activity?
Ari: I have not implemented this with any locks. Basically, I didn’t want to write it in such a way that modifying objects would be a problem from the ResultReceiver
. In my particular use case, I’m avoiding locking, and I have two things that are going on. The first is that I’m changing the state on a little progress spinner, which is not actually thread safe to modify. But, because execution is passing back to the UI thread, that is OK. I’m asking a UI thread method in the Activity
to make that change. The next thing that I’m doing is finishing the login activity, which again, is safe to do on the UI thread. I’m avoiding the problem by not doing a whole lot in that method. If you actually did have a need to update a lot of things synchronously, then I would be concerned about it, I’ve just tried to keep my needs really minimal.
Q: Did you think about using an EventBus to separate the Service
from the component that started the Activity
?
Ari: To be honest, I didn’t, and I would love to talk about that further.
Q: How can we understand intuitively why Android was designed this way with Service
s, Sync Adapter, AsyncTask
and handlers?
Stephan: The battery is a main concern, especially because there is no fan on a phone. If you have many things going on and you put it in your pocket, it will start to burn you. In Linux or on a PC, memory is so cheap. You can do whatever you want because you have a lot of RAM, a cooling fan, and a lot of space. In Android, you don’t have that. A lot of it comes down to battery.
Suyash: I just wanted to add something interesting. When I first jumped into Android, you first try to do everything in the main thread. If you’ve done any server-side programming in Java or JavaScript programming, you don’t have to really worry about multi-threading until later. But to avoid that, your brain starts to learn about how to handle new threads. Android has this system in place with Services
, AsyncTask
, and even handlers. So, it’s basically because of performance reasons because everything shouldn’t happen in the UI thread. I think it’s genius, because I haven’t seen it in iOS. AsyncTask
is pretty unique.
Ari: Both of those statements help contextualize what I wanted to say. My experience of it is that there is a very intuitive and manual learning process to go through when you start running into performance issues. I’m very sympathetic to having to acquire a lot of information. My take on it at this point is that Java just gives you a lot of rope to hang yourself with. Stephan was alluding to battery usage and performance, but I want to add to that. As a programmer, it’s very easy to just create new threads if the solution is to run tasks on a different thread. Then you’ll end up with locking issues and thread non-safety issues. If you forget to manage the lifecycle of these processes yourself, then they still run in Android run time while you have moved on obliviously. So, as difficult as it is to get started with an understanding of these concepts, it is actually preferable to having to do all of the heavy lifting yourself.
Q: The user might have to wait while the login occurs, so what are some ways that you deal with letting a process run while also being responsive to what the user wants?
Ari: In the Mango app, we resolve this by interfering with the soft back button, which is the back arrow at the top in the toolbar. A dialogue message will ask the user if they can stay on the view for a minute. The other alternative is to let that back button succeed, but give your Activity
an interstitial state. We can then make the application load in such a way that the user will see that they have been able to go back. They can see the previous screen, but they aren’t seeing the full app yet. Then we have to give them some feedback that the process is finishing and wait another minute. That way, if they hit home, they go to the home screen and the login process continues to work.
This is very important actually, because we still want the login process to work. The advantage of starting this up in the Service
model is that it will work. The task has been de-prioritized, but hasn’t been killed. Because of that potential, we also have to be really careful that of anything that we might want to do in the UI thread later. It’s not very extensive, and doesn’t presume the existence of the activity. You could do something in your ResultReceiver
, like setting the progress spinner to visible if it’s still there. I think it feels a little bit hacky to do it that way, and I’d love to actually come up with a better solution.
Receive news and updates from Realm straight to your inbox