Android: Closing those database objects

(Or, what the Notepad tutorial never told you.)

If you’ve used Android’s Notepad tutorial (Version 3) , or modeled your own Activity on Notepadv3, then you’ve probably seen log messages that look something like this:

android.database.sqlite.DatabaseObjectNotClosedException: 
    Application did not close the cursor or database object that was opened here
at android.database.sqlite.SQLiteCompiledSql.<init>(SQLiteCompiledSql.java:62)
at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:80)
at android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.java:36)
at android.database.sqlite.SQLiteDatabase.compileStatement(SQLiteDatabase.java:1145)
at android.database.sqlite.SQLiteDatabase.updateWithOnConflict(SQLiteDatabase.java:1671)
at android.database.sqlite.SQLiteDatabase.update(SQLiteDatabase.java:1622)
at com.android.demo.notepad3.NotesDbAdapter.updateNote(NotesDbAdapter.java:186)
at com.android.demo.notepad3.NoteEdit.saveState(NoteEdit.java:106)
at com.android.demo.notepad3.NoteEdit.onPause(NoteEdit.java:87)
at android.app.Activity.performPause(Activity.java:3842)
at android.app.Instrumentation.callActivityOnPause(Instrumentation.java:1190)
at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3335)
at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3305)
at android.app.ActivityThread.handlePauseActivity(ActivityThread.java:3288)
at android.app.ActivityThread.access$2500(ActivityThread.java:125)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2044)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4627)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
at dalvik.system.NativeStart.main(Native Method)

This is resolved easily enough by overriding the onDestroy method in each of the Activities in your application — or at least, each Activity that uses the database. In the Notepadv3 app, this means overriding onDestroy in both the Notepadv3 and NoteEdit classes. The same definition is suitable for both classes:

    public void onDestroy() {
        super.onDestroy();

        // Replace mDbHelper as needed with your database connection, or
        // whatever wraps your database connection. (See below.)
        mDbHelper.close();
    }

In the Notepadv3 tutorial, the mDbHelper field is an instance of the class NotesDbAdapter, which wraps the database connection. NotesDbAdapter comes from google with the close method already defined, but there’s no code that calls it. With this change, we’re supplying the code that calls close at the right time. (I think.)

In general: If your activity opens a database by calling either SQLiteOpenHelper.getReadableDatabase or SQLiteOpenHelper.getWritableDatabase, then you should rely on SQLiteOpenHelper.close to close that database. Your activity’s onDestroy method must result in a call to the close method for any instances of SQLiteOpenHelper, or classes derived from it, that you’ve created. (In the code above, mDbHelper.close will have that effect.) SQLiteOpenHelper.close will then close any database handle that it created for you.

, , ,

2 Comments

  • Danny says:

    Hi Dan,

    while going through the notepad code I too noticed the close function never being called. However I don’t think it should be triggered within the onDestroy method but in the onPause method. As the developer pages state, there is no guarantee the onDestroy is being called.
    http://developer.android.com/reference/android/app/Activity.html#onDestroy%28%29

    Also, as the connection is closed onPause, the connection should be recreated in the onResume.

  • Dan Breslau says:

    Hi, Danny,

    You raise a good point: Should we rely on the onClose method to close the database, given that onClose may never be called?

    I think that the answer is yes. Here’s why:

    For starters, note that the database handle is first used within the Notepadv3.onCreate method, which in turn calls fillData to populate the list of notes; fillData relies on a database cursor to do its work. That implies that the onCreate method needs to support having the database handle opened.

    Maybe we could push that logic into the onResume method, so that we don’t populate the list until after the database is opened, but that’s not what onResume is really meant for. According to the Activity Lifecycle documentation, the onCreate method is typically where the application would create views, bind data to lists, etc. (I’d guess that these actions are performed in onCreate rather than in onResume because the latter may be called more frequently, and performance is best served by minimizing the number of times we perform these actions.) Once again, this implies that the database connection would need to be opened at some point within onCreate.

    And what’s created in onCreate should, in general, be destroyed in onDestroy.

    On the flip side, what is the harm if the process is killed without closing the database handle? None, really. SQLite itself promises that the database won’t be corrupted if the process goes down suddenly, even in the middle of an update. This means that the only other real need for closing the database is to ensure that in-process resources (locks, memory buffers, etc.) are cleaned up appropriately — none of which will really matter if the process is being terminated by the OS.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>