在 ContentProvider 中关闭数据库

本周,我学习了 ContentProvider 的所有知识,并使用 SQLiteOpenHelper 类管理提供程序内部数据库的创建和升级。具体来说,我已经从 sdk 的示例目录中通读了 NotePad 示例。

现在,我可以看到 SQLiteOpenHelper 有一个 close ()方法。我知道保持空闲数据库打开是不好的做法,可能会导致内存泄漏等等(除非 这个讨论朝着正确的方向发展)。如果我在 Activity 中使用该类,那么我只需在 onDestroy ()方法中调用 close () ,但据我所知,ContentProvider 的生命周期与 Activity 不同。NotePad 的代码似乎从来没有调用 close () ,所以我想假设它是由 SQLiteOpenHelper 或其他一些难题处理的,但我真的想知道确切的答案。我也不太相信样本代码。

问题摘要: 什么时候应该关闭提供程序中的数据库(如果有的话) ?

24951 次浏览

完成后关闭它,最好是在 finally 块中,这样就可以确保它的发生。我知道这听起来有点陈词滥调,但这是我唯一知道的答案。如果您打开数据库并执行某个操作,则在完成该操作时关闭它,除非您确切知道将再次需要它(在这种情况下,确保在不再需要它时关闭它)。

如果您在活动中使用内容提供程序,那么我认为您不必维护内容提供程序的连接。您可以只管理使用 startManagingCursor 返回的游标对象。在活动的 onPuse 方法中,您可以释放内容提供程序。(可以在 onResume 中重新加载)。假设活动生命周期通常是有限的,这就足够了。(至少我是这么认为的)

如果希望数据库自动关闭,可以在打开数据库时提供 CursorFactory:

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

课程如下:

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();
}
}
}

我遵循 曼纳兹的的答案,看到 SQLiteCursor(database, driver, table, query);构造函数是不推荐的。然后我找到了 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);
}
}

这个问题有点老,但仍然相当重要。请注意,如果您正在以“现代”的方式做事情(例如,使用 LoaderManager 和创建 CursorLoader 在后台线程中查询 ContentProvider) ,请确保您不要在 ContentProvider 实现中调用 Close ()。当 CursorLoader/AsyncTaskLoader 试图在后台线程中访问 ContentProvider 时,我遇到了各种与 CursorLoader/AsyncTaskLoader 相关的崩溃,这些崩溃通过删除 db.close ()调用得到了解决。

所以如果你遇到这样的崩溃(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

或者这样(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

或者,如果你在 LogCat 看到的错误消息是这样的:

Cursor: invalid statement in fillWindow()

然后检查 ContentProvider 实现,并确保没有过早关闭数据库。根据 这个,ContentProvider 会在进程终止时自动清理,所以您不需要提前关闭它的数据库。

也就是说,确保你仍然是正确的:

  1. 关闭从 Query ()返回的游标。(CursorLoader/LoaderManager 会自动为你做这些,但是如果你在 LoaderManager 框架之外进行直接查询,或者你已经实现了一个定制的 CursorLoader/AsyncTaskLoader 子类,你必须确保你正在正确地清理你的游标
  2. 以线程安全的方式实现 ContentProvider。(最简单的方法是确保数据库访问方法封装在 同步块中。)

根据 Dianne Hackborn (Android 框架工程师)的说法,在内容提供商中没有必要关闭数据库。

内容提供程序是在创建其宿主进程时创建的,并且 只要这个过程继续进行下去,就没有必要 关闭数据库——它将作为内核的一部分被关闭 在进程被终止时清理进程的资源。

谢谢@bigstone 指出这一点。