This week I've been learning all about ContentProvider and using the SQLiteOpenHelper class to manage the creation and upgrading of the database inside of a provider. Specifically, I've been reading through the NotePad example from the sdk's samples directory.
这周我学习了所有关于ContentProvider的知识,并使用SQLiteOpenHelper类来管理提供者内部数据库的创建和升级。具体地说,我一直在阅读sdk示例目录中的记事本示例。
Now, I can see that SQLiteOpenHelper has a close() method. I'm aware that leaving idle databases open is bad practice and can cause memory leaks and whatnot (unless this discussion is headed in the right direction). If I were using the class in an Activity, then I would simply call close() in the onDestroy() method, but as far as I know, ContentProvider does not have the same life cycle that activities do. The code for NotePad never seems to call close(), so I would like to assume that it is handled by SQLiteOpenHelper or some other piece of the puzzle, but I'd really like to know for sure. I don't really trust the sample code that much, either...
现在,我可以看到SQLiteOpenHelper有一个close()方法。我知道,让空闲的数据库保持打开状态是不好的做法,可能会导致内存泄漏等问题(除非讨论朝着正确的方向进行)。如果我在活动中使用这个类,那么我只需在onDestroy()方法中调用close(),但是据我所知,ContentProvider没有活动所具有的相同的生命周期。NotePad的代码似乎从来没有调用close(),所以我想假设它是由SQLiteOpenHelper或其他一些难题处理的,但我确实想知道。我也不太信任示例代码……
Question summary: When should we close the database in a provider, if at all?
问题摘要:我们应该何时关闭提供者中的数据库?
89
According to Dianne Hackborn (Android framework engineer) there is no need to close the database in a content provider.
根据Dianne Hackborn (Android框架工程师)的说法,内容提供者不需要关闭数据库。
A content provider is created when its hosting process is created, and remains around for as long as the process does, so there is no need to close the database -- it will get closed as part of the kernel cleaning up the process's resources when the process is killed.
内容提供程序是在它的宿主进程被创建时创建的,并且在进程执行时仍然存在,因此不需要关闭数据库——当进程被杀死时,它将作为内核清理进程资源的一部分关闭。
Thanks @bigstones for pointing this out.
感谢@bigstones指出这一点。
21
This question is a bit old but is still quite relevant. Note that if you're doing things the 'modern' way (e.g. using LoaderManager and creating CursorLoaders to query a ContentProvider in a background thread), make sure that you do NOT call db.close() in your ContentProvider implementation. I was getting all sorts of crashes relating to CursorLoader/AsyncTaskLoader when it tried to access the ContentProvider in a background thread, which were resolved by removing the db.close() calls.
这个问题有点旧,但仍然很重要。注意,如果您采用“现代”方式(例如使用LoaderManager并创建游标加载程序来查询后台线程中的内容提供程序),请确保在您的内容提供程序实现中不调用db.close()。当它试图访问后台线程中的内容提供程序时,我遇到了与CursorLoader/AsyncTaskLoader相关的各种崩溃,通过删除db.close()调用来解决这个问题。
So if you're running into crashes that look like this (Jelly Bean 4.1.1):
如果你遇到这样的崩溃(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):
或者这个(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're seeing error messages in LogCat that look like this:
或者如果你在LogCat中看到错误信息是这样的:
Cursor: invalid statement in fillWindow()
Then check your ContentProvider implementation and make sure you're not closing the database prematurely. According to this, the ContentProvider will get cleaned up automatically when the process is killed anyway, so you don't need to close its database ahead of time.
然后检查您的ContentProvider实现,确保没有提前关闭数据库。根据这一点,无论如何,当进程被终止时,ContentProvider将被自动清理,因此您不需要提前关闭它的数据库。
That said, make sure you are still correctly:
这就是说,确保你仍然正确:
13
Ive follow Mannaz's answer and saw that SQLiteCursor(database, driver, table, query);
constructor is deprecated. Then I found getDatabase()
method and used it instead of mDatabase
pointer; and kept constructor for backward capability
我按照曼纳兹的回答,看到了SQLiteCursor(数据库、驱动程序、表、查询);构造函数是弃用。然后找到getDatabase()方法,用它代替mDatabase指针;并保持构造函数的向后能力
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);
}
}
7
If you want your Database to close automatically you can provide a CursorFactory
when opening it:
如果您希望您的数据库自动关闭,您可以在打开时提供一个CursorFactory:
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();
}
}
}
1
Close it when you are done with it, preferably in a finally block so you can ensure that it happens. I know that sounds a little trite and off-the-cuff, but it's really the only answer that I know of. If you open the database and perform an action, close it when you're done with that action unless you know for a fact it will be needed again (in which case be sure to close it once its no longer needed).
完成后关闭它,最好是在最终块中,这样您就可以确保它的发生。我知道这听起来有点陈腐和即兴,但这确实是我所知道的唯一答案。如果您打开数据库并执行一个操作,那么在执行该操作时关闭它,除非您知道再次需要它(在这种情况下,确保在不再需要它时关闭它)。
0
If you are using your content provider within a activity, then I do not believe that you have to maintain the connection of the content provider. You could just manage the cursor object returned using startManagingCursor. In the onPause method of activity, you can release the content provider. ( you can reload it in onResume). Assuming that the activity life cycle will usually be limited, this would suffice. (Atleast according to me ;))
如果您在活动中使用内容提供程序,那么我认为您不必维护内容提供程序的连接。您可以使用startManagingCursor管理返回的游标对象。在活动的onPause方法中,可以释放内容提供程序。(你可以重新载入onResume)。假设活动生命周期通常是有限的,这就足够了。(至少我是这么认为的;)