Closing a database in ContentProvider

This week I learned all about ContentProvider and the use of the SQLiteOpenHelper class to control the creation and updating of a database within a provider. In particular, I read the NotePad example from the sdk sample directory.

Now I see that SQLiteOpenHelper has a close () method. I know that abandoning open databases is bad practice and can cause memory leaks and more (if this discussion is not directed). If I used the class in Activity, then I would just call the close () method in the onDestroy () method, but as far as I know, the ContentProvider does not have the same life cycle as the actions. The code for NotePad never calls the close () call, so I would like to assume that it is being processed by SQLiteOpenHelper or some other part of the puzzle, but I really would like to know for sure. I really don't really trust the sample code, either ...

Summary of the question: when do I need to close the database with the provider, if at all?

+72
android android-contentprovider sqlite
Dec 28 '10 at 16:19
source share
6 answers

According to Dianne Hackborn (developer of Android flash memory), there is no need to close the database in the content provider.

A content provider is created when its hosting process is created, and stays around as long as the process is running, so there is no need to close the database - it will be closed as part of the kernel cleaning up the resources of the process when the process is killed.

Thanks to @bigstones for pointing this out.

+89
03 Oct '12 at 18:59
source share

This question is a little old, but still very relevant. Note that if you are doing something in a β€œmodern” way (for example, using LoaderManager and creating CursorLoaders to request a ContentProvider in the background thread), make sure you do not call db.close () in your implementation of ContentProvider. I got all kinds of crashes related to CursorLoader / AsyncTaskLoader when it tried to access the ContentProvider in the background thread, which were fixed by removing the db.close () calls.

So, if you run into crashes like this (Jelly Bean 4.1.1):

Caused by: java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed. at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962) at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:677) at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348) at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894) at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:834) at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62) at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:143) at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133) at android.content.ContentResolver.query(ContentResolver.java:388) at android.content.ContentResolver.query(ContentResolver.java:313) at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:147) at com.hindsightlabs.paprika.loaders.GroceryListLoader.loadInBackground(GroceryListLoader.java:1) at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40) at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) ... 4 more 

Or this (ICS 4.0.4):

 Caused by: java.lang.IllegalStateException: database /data/data/com.hindsightlabs.paprika/databases/Paprika.db (conn# 0) already closed at android.database.sqlite.SQLiteDatabase.verifyDbIsOpen(SQLiteDatabase.java:2215) at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:436) at android.database.sqlite.SQLiteDatabase.lock(SQLiteDatabase.java:422) at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:79) at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:164) at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:156) at android.content.ContentResolver.query(ContentResolver.java:318) at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:49) at android.support.v4.content.CursorLoader.loadInBackground(CursorLoader.java:35) at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:240) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:51) at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:40) at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:123) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305) ... 4 more 

Or if you see error messages in LogCat that look like this:

 Cursor: invalid statement in fillWindow() 

Then check the implementation of ContentProvider and make sure that you are not closing the database prematurely. Accordingly , the ContentProvider will be automatically cleared when the process is killed, so you do not need to close its database ahead of time.

However, make sure you are still correct:

  • Closing your cursors that return from ContentProvider.query () . (CursorLoader / LoaderManager does this automatically for you, but if you run direct queries outside the scope of LoaderManager or you have implemented your own subclass CursorLoader / AsyncTaskLoader, you need to make sure that you clear your cursors properly).
  • Implementing your ContentProvider in a thread-safe manner. (The easiest way to do this is to make sure that the database access methods are wrapped in a synchronized block.)
+21
Aug 09 2018-12-12T00:
source share

Ive followed Mannaz's answer and saw that the constructor is SQLiteCursor(database, driver, table, query); outdated. Then I found the getDatabase() method and used it instead of the mDatabase pointer; and saved the constructor for the reverse possibility

 public class MyOpenHelper extends SQLiteOpenHelper { public static final String TAG = "MyOpenHelper"; public static final String DB_NAME = "myopenhelper.db"; public static final int DB_VESRION = 1; public MyOpenHelper(Context context) { super(context, DB_NAME, new LeaklessCursorFactory(), DB_VESRION); } //... } public class LeaklessCursor extends SQLiteCursor { static final String TAG = "LeaklessCursor"; public LeaklessCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable, SQLiteQuery query) { super(db, driver, editTable, query); } @Override public void close() { final SQLiteDatabase db = getDatabase(); super.close(); if (db != null) { Log.d(TAG, "Closing LeaklessCursor: " + db.getPath()); db.close(); } } } public class LeaklessCursorFactory implements CursorFactory { @Override public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) { return new LeaklessCursor(db,masterQuery,editTable,query); } } 
+13
Jan 28 '12 at 11:08
source share

If you want your database to close automatically, you can provide a CursorFactory when you open it:

 mContext.openOrCreateDatabase(DB_NAME, SQLiteDatabase.OPEN_READWRITE, new LeaklessCursorFactory()); 

Here are the classes:

 public class LeaklessCursorFactory implements CursorFactory { @Override public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) { return new LeaklessCursor(db,masterQuery,editTable,query); } } public class LeaklessCursor extends SQLiteCursor { static final String TAG = "LeaklessCursor"; final SQLiteDatabase mDatabase; public LeaklessCursor(SQLiteDatabase database, SQLiteCursorDriver driver, String table, SQLiteQuery query) { super(database, driver, table, query); mDatabase = database; } @Override public void close() { Log.d(TAG, "Closing LeaklessCursor: " + mDatabase.getPath()); super.close(); if (mDatabase != null) { mDatabase.close(); } } } 
+7
Jan 26 '11 at 20:37
source share

Close it when you're done with it, preferably in the finally block, so you can make sure that this happens. I know this sounds a little corny and unmistakable, but this is really the only answer that I know of. If you open a database and perform an action, close it when you finish this action, if you do not know that it will be necessary again (in this case, be sure to close it as soon as it is no longer needed).

+1
Jan 04 '11 at 17:52
source share

If you use your content provider as part of an action, I don’t think you need to maintain a content provider connection. You can simply control the cursor object that was returned using startManagingCursor. In the onPause method, you can free the content provider. (you can reload it in onResume). Assuming that the activity life cycle will usually be limited, this will be enough. (At least for me;))

0
Jan 20 2018-11-11T00:
source share



All Articles