Android SQLite SQLiteOpenHelper IllegalStateException - DB Already Closed Error

SBerg413 picture SBerg413 · Jun 30, 2011 · Viewed 10.8k times · Source

This has been driving me crazy for a few days now. I have an android application that is pretty complex. It uses multiple threads to pull data from a server and populate the SQLite database. I'm using a singleton to reference my extension of SQLiteOpenHelper. I'm opening and closing the database in each of my activities.

The error ONLY occurs in a situation when I'm 4 activities deep and then try to back out. I've tried various ways of opening and closing the database, including moving the close from the onDestroy() to the onPause() methods and also adding another open to the onResume().

Also of note, my activities make heavy use of ListViews and ExpandableListViews, which I understand could cause the database to close based on this article: http://darutk-oboegaki.blogspot.com/2011/03/sqlitedatabase-is-closed-automatically.html

I've gone through the code and made sure i'm either closing all my cursors, or, if they are being assigned to an adapter, calling startManagingCursor().

Does anybody have a clue as to what's going on?

java.lang.RuntimeException: Unable to resume activity {com.fieldone/com.fieldone.DispatchActivity}: java.lang.IllegalStateException: database /data/data/com.fieldone/databases/InterstateAirConditioning-1602814322.db already closed
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3347)
    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3362)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2162)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:144)
    at android.app.ActivityThread.main(ActivityThread.java:4937)
    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)
Caused by: java.lang.IllegalStateException: database /data/data/com.fieldone/databases/InterstateAirConditioning-1602814322.db already closed
    at android.database.sqlite.SQLiteProgram.bindString(SQLiteProgram.java:237)
    at android.database.sqlite.SQLiteQuery.requery(SQLiteQuery.java:145)
    at android.database.sqlite.SQLiteCursor.requery(SQLiteCursor.java:567)
    at android.app.Activity.performRestart(Activity.java:3836)
    at android.app.Activity.performResume(Activity.java:3857)
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3337)
    ... 10 more

UPDATE: I've fixed the problem, but not sure WHY this fixed it. So, maybe someone out there knows or could explain.

When I'm in the 4th activity of the stack of activities, I was attempting to close the db via db.close(). No matter WHERE I put this, in the onCreate after I get the data i need, or in the onStop or onDestroy, it will produce this error. If I DO NOT close the db, I'm not having the problem. So, something is causing the db to automatically close. The strange thing is, although I'm using an expandableListView in this final activity, I'm NOT using a cursorAdapter. Anyone have any thoughts? Would love to understand this.

Answer

SBerg413 picture SBerg413 · Oct 27, 2011

Since this question has gotten a good deal of attention, I wanted to answer it and describe what I've learned and what worked for me to correct this problem in my large, database-driven application:

  1. Do NOT use managed cursors. There's a reason why they're deprecated. They're completely problematic. Realistically, there's very few scenarios where you actually need to use a managed cursor anyway. Instead, run your query and populate an object with the results. If you're querying for multiple row, create an ArrayList<> of your object to hold all the rows. What I do now is create a function that runs the query and passes me back my ArrayList<> rather than a cursor in the return. I close the cursor within the function and I'm done. For a ListViews, you'll no longer be able to use a SimpleCursorAdapter. Just convert all of these to a BaseAdapter and use your ArrayList<> object to populate it.

  2. DO NOT FORGET TO CLOSE ALL YOUR CURSORS. This can also wreak havoc on your app's db connections. I thought I was doing this but, sure enough, found a spot that I wasn't explicitly closing a cursor. So, go through your app and double check all of them.

I'm also using a singleton DatabaseHelper object. I declare a static DatabaseHelper object within my SQLiteOpenHelper class so that I'm getting the same instance every time.

I now have a stable running app that no longer gets these DB errors. I hope this information is helpful to some of you.