Search code examples
androidsqlitenullpointerexceptionlifecycleonresume

Changes to SQLite database lifecycle between Gingerbread and ICS; intermittent null pointer exception


I noticed that after I upgraded to ICS from Gingerbread my app started crashing in circumstances when it had worked satisfactorily under Gingerbread.

The app's main activity (activity A) has a menu option which opens a ListActivity (activity B) populated from a database; when an entry in the list is clicked a third activity (activity C) is opened. When activity C is closed with the Back button activity B is supposed to be redisplayed.

In Gingerbread (and earlier Android versions) this worked fine, and continued to work OK in ICS most of the time. However, it crashed if I navigated away from the app leaving activity C open for an extended period. On returning to the app and trying to back out of activity C to activity B the app would stop. LogCat reported "unable to resume activity" because of a null pointer exception in onResume in activity B.

The offending line in the onResume method contains a reference to a DBAdapter which is defined in the onCreate method of activity A. By logging calls to activity B's various lifecycle methods I found that activity B is always (as expected) stopped when activity C is opened. Provided it is only stopped there is no problem: the DBAdapter must still be defined when onResume is called. However, if the app is left for a longer period activity B is destroyed and in these circumstances the DBAdapter (defined in the onCreate method of activity A) must also become undefined.

A fix for this problem seems to be to insert if (Global.mDBAdapter== null) {Global.mDBAdapter = new DBAdapter( this, "DatabaseName" );} into the onCreate method of activity B.

I would like to check that I have understood this correctly. Is there documentation that describes the change (between Gingerbread and ICS) in lifecycle behaviour of a SQLite database adapters/helpers?


Solution

  • It seems you were making assumptions that weren't warranted, and you just happened to be getting away with them on your Gingerbread device.

    An Activity's process is always killable any time after onStop() has been called, so at the end of onStop(), you must be prepared for your next lifecycle callback to be any of these three:

    1. onRestart() -- If the process isn't killed and the Activity is restarted before long.
    2. onDestroy() -- If the system has decided to stop the activity and finds it convenient to let you know.
    3. onCreate() -- If the process was killed without calling onDestroy().

    (Pre-Honeycomb, the process is even killable after onPause().)

    So what it sounds like is happening is that when you've navigated away from Activity C for a long period, the whole process is killed. When you go back to Activity C, then press BACK, Activity B goes through onCreate() -> onStart() -> onResume(), and you seem to be assuming you have global state set up by Activity A, which isn't the case.

    This wasn't a change between Gingerbread and ICS; you were just getting lucky before.

    Good resources to read and understand:

    Update: You asked in the comments below what exactly Android retains about your application state when it is destroyed. My impression is that it is basically just the Activity stack and any Bundles from onSaveInstanceState(). (It doesn't specifically remember which Activity was open, that's just the top of the stack.) That makes sense: Android handles its end of things (the Activity stack) and you handle yours (by saving what's most important and being prepared to recreate the rest).

    The default implementation of onSaveInstanceState() calls the method of the same name on all of your Views, which can make it seem like you're getting some data saved and restored for free. Without overriding that method, however, you certainly won't have any of your own data members saved, static or otherwise. That makes a lot of sense to me: automatically doing so would require reflection and would be inefficient, often wasteful, and quite confusing. It's up to you to save what's important to be saved, and Android gives you the callbacks to make this easy.

    Here's another resource for you:

    You don't necessarily need to deal with onSaveInstanceState(), however; some things make much more sense to be recreated, e.g. your database adapter.