Android: Closing those database objects
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.
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.
Hi, Danny,
You raise a good point: Should we rely on the
onClose
method to close the database, given thatonClose
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 callsfillData
to populate the list of notes;fillData
relies on a database cursor to do its work. That implies that theonCreate
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 whatonResume
is really meant for. According to the Activity Lifecycle documentation, theonCreate
method is typically where the application would create views, bind data to lists, etc. (I’d guess that these actions are performed inonCreate
rather than inonResume
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 withinonCreate
.And what’s created in
onCreate
should, in general, be destroyed inonDestroy
.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.