- Android Development: Database Upgrades
- Android Development: Missing API
- (Not) Integrating Application with Intents
- Android Activity Lifecycle
- URI Matching in Android’s IntentFilters
- Android Intent Sender Verification
- Android Jittery Scrolling Gallery
- Encryption on Android & BouncyCastle
- Android Permission Handling
- ContentProvider Curses Cursor
- ContentProvider Wasted Potential
It saddens me to say this, but Android’s Activity Lifecycle is broken by design.
It saddens me, because I enjoy developing for Android, and would like to be able to only say good things about it. But that doesn’t mean there aren’t bad sides either. Read on if any of this is of interest to you.
First of all, let me get one thing out of the way: I’m actually really quite happy with the Android team’s decision to introduce Activities. They’re a great way for breaking an app into re-usable parts, and thus encouraging integrating apps.
What’s broken about Activities is pretty much only their lifecycle management, and reading between the lines it seems as if it’s implementation has been one of those moments where developers didn’t quite finish designing a brilliant initial idea, but instead decided on implementing it straight away1.
The diagram and associated documentation I linked to in the first paragraph makes it appear as if the Activity lifecycle is actually quite simple. And, used naively, it actually is. You set up stuff in
onCreate() and tear down stuff in
Well… yes, you can do that. There’s a caveat, though, in that Activities rarely exist on their own. They tend to spawn other Activities. One common scenario is for one Activity to display a list of items, and upon clicking on one of those items, another Activity gets launched that lets you edit the item you clicked on. When you’re done with editing, that second Activity gets closed, and the first resumes. Great stuff.
What happens, though, if any of the data that got changed in the edit Activity requires a refresh of the list items in the list Activity? For example, your list might be ordered by item names, and that’s exactly the field you edited. Not only does the content of one list item need to be refreshed, the whole list’s appearance is altered — that’s pretty much the worst case you can get, which is why it’s useful to use it as an example.
So you need to redraw the list when the Activity gets resumed. That’s perfect, there’s an
onResume() and a corresponding
onPause() that look as if they were made for exactly this purpose. Problem solved. Right?
Well… maybe. If you can fill the list reasonably fast, then yes, you’re in pretty good shape. The issue becomes a lot more complex if you can’t — possibly because you’re fetching the list data from the internet. Given that Google promotes Android devices as designed to be always connected2, a list being filled with data from the internet seems like a fairly reasonable assumption.
Now things are getting a bit uglier. You might reasonably assume that you can fetch data from the internet in
onResume(). But that won’t work except in ideal cases, because — much like on the iPhone — an Activity that does not respond to the runtime for a while is assumed to have hung, and Android suggests that you force kill it. That pretty much forces you to perform your network request in a separate thread, by whichever means. Which is great, you should do that anyway… but it opens another can of worms.
A number of issues need to be addressed at this point:
- Some form of progress indicator should be shown. It’s not strictly speaking required, but good UI design.3
- If the Activity is paused, or worse yet destroyed, what happens to the request that’s been fired off?
- Requests can take arbitrarily long… you don’t want to fire off a new request without good cause, so you’ll need to cache the response data somewhere.
The first points is just an annoyance, the second more of an optimization. Sure we can easily write code such that unnecessary responses just go unhandled. Or we can even interrupt the fetcher thread. Neither of those two points are pretty to solve, but they’re solvable.
The last point, however, takes a lot more to consider.
Let me backpedal a bit, and quickly mention that when starting a second Acitivty, that Activity can return a result that could tell us whether to we need to refresh data, or — in combination with the knowledge which Activity returned that status — how much we need to do to bring the first Activity’s contents up-to-date.
But after an second Activity’s results are handled, the first Activity’s
onResume() is called. Now we can’t just perform our construction stuff in
onResume(); what we’ll need to do in order to avoid unnecessary network calls is to set some flag when handling the second Activity’s return value that tells
onResume() what to do. Suddenly you have the beginnings of a (so far still simple) state machine on your hands.
So with a small amount of fugly state handling, we can decide reasonably well what to do in
Except we’ve been ignoring the fact that we shouldn’t really fetch data from the network more than necessary, so we’ll have to cache the results of such requests as best as possible. Luckily,
onSaveInstanceState() comes to the rescue. This insiduous little function is called whenever the runtime decides that an Activity is about to be destroyed, and here you can persist all data members you might wish to restore later on.
- Although I’ve been guilty of that myself, many times over, I’m never very proud of it — it’s often a necessity, rarely enjoyable in my opinion. [↩]
- Which, even in large cities like London, is frankly rather silly. Googler’s can’t be using the Tube, I think, or they’d have realized the flaw in that assumption. [↩]
- Note that AdapterView, from which ListView is derived, conveniently provides for empty views for cases when the list has no content. But there is a third state, “content pending”, that you’ll end up handling manually all the time because it’s not provided for. [↩]