I spent last week rewriting the database backend and boy was it a convoluted mess.
First, I tried salvaging the existing framework but I couldn’t figure out what the heck it was doing. Then I tried an implementation using ThreadPools, but handling configuration changes meant re-inventing the wheel. (Re-inventing the wheels means that there’s a good chance the code might break in future Android versions.) Then I tried a version using DAOs. Unfortunately, the “Android way” (using Cursors) gives better performance, so I started again from scratch.
But what is the Android way? and what did I really want?
I like writing code that uses KISS (Keep It Simple Stupid). For me, KISS means that each class does one thing and one thing well. No “swiss-army knife” classes, no convoluted methods that do everything and anything. KISS applied to code usually results in code that’s easy to understand and maintain.
It was obvious that the database would need to do things the Android way. Not only for performance but also to make it easier to understand and maintain. Yeah, you read that right. Imagine yourself going back to a project after several months. Using Android classes and frameworks (and in the intended way) means you’ll spend less time relearning the project.
Android has the peculiar property that it restarts applications when the user does something “unexpected” like putting the phone on its side. The database framework would need to handle configuration changes. (Imagine, for example, rotating the phone while updating hundreds of tasks.)
It also goes without saying that the database framework would need to run on a thread other than the UI. (Imagine, for example, updating hundreds of tasks. You’ll get an ANR if it takes more than 5 seconds.)
Putting it all together, here are the guiding principles for the new database framework:
- It should be easy to understand and maintain. Use existing Android classes whenever possible.
- It should be generic enough so that the “database” can be a sqlite database or something else.
- It should know about configuration changes (like changing the phone orientation).
- It should be multi-thread safe. By “thread safe”:
- it should NOT run in the main UI thread,
- database calls should not conflict with each other
- the system should know how to communicate results back to the main UI thread.
The New Database Framework
First, what does NOT work:
- AsyncTask – unfortunately, AsyncTask doesn’t know about configuration changes.
- headless fragments – Fragments without a UI. You basically have to re-implement the AsyncTaskLoader framework so that defeats the point.
The new system is based on AsyncTaskLoader. It’s actually pretty simple:
- a TaskContentProvider provides low-level access to the database. All the CRUD and only the CRUD.
- a TaskDatabaseFacade provides hi-level access to the database. So, for example, the createTask method not only creates a task using TaskContentProvider, it also updates the alarm system. (In other words, the TaskDatabaseFacade is the CRUD + business logic.) TaskDatabaseFacade is actually a collection of AsyncTaskLoaders, one per action.
And that’s it! If I need to store the database somewhere else (say Google drive or Dropbox), TaskDatabaseFacade can use a different loader. If I need to open up the ContentProvider to the rest of the Android system, I can just implement another one (remember KISS).
Handling multi-threading is still a work in progress. Before HoneyComb, all loaders run in parallel, leading to data consistency issues if you don’t handle multi-threading correctly. After HoneyComb, it’s still an issue if you run another background thread that updates the database at the same time as the UI.
That’s basically it. Checkout the work in progress in the newDB branch over at GitHub. It’s currently more of a proof of concept—insertion and reads work.