在ContentProvider中关闭数据库

时间:2010-12-28 16:19:16

标签: android sqlite android-contentprovider

本周我一直在学习有关ContentProvider的所有内容,并使用SQLiteOpenHelper类来管理提供程序内部数据库的创建和升级。具体来说,我一直在阅读sdk的samples目录中的NotePad示例。

现在,我可以看到SQLiteOpenHelper有一个close()方法。我知道将空闲数据库打开是不好的做法,可能导致内存泄漏等等(除非this讨论朝着正确的方向前进)。如果我在Activity中使用该类,那么我只需在onDestroy()方法中调用close(),但据我所知,ContentProvider与活动没有相同的生命周期。 NotePad的代码似乎永远不会调用close(),所以我想假设它是由SQLiteOpenHelper或其他一些拼图处理的,但我真的很想知道。我不太相信示例代码,或者......

问题摘要:我们应该何时关闭提供商中的数据库,如果有的话?

6 个答案:

答案 0 :(得分:90)

According to Dianne Hackborn(Android框架工程师)无需在内容提供商中关闭数据库。

  

在创建托管进程时创建内容提供程序,并且   只要这个过程一直存在,所以没有必要   关闭数据库 - 它将作为内核的一部分关闭   在进程被杀死时清理进程的资源。

感谢@bigstones指出这一点。

答案 1 :(得分:21)

这个问题有点陈旧,但仍然非常相关。请注意,如果您使用“现代”方式(例如,使用LoaderManager并创建CursorLoaders以在后台线程中查询ContentProvider),请确保不要在ContentProvider实现中调用db.close()。当我尝试访问后台线程中的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实现,确保您没有过早关闭数据库。根据{{​​3}},无论如何都会在进程被终止时自动清理ContentProvider,因此您不需要提前关闭其数据库。

那就是说,确保你仍然正确:

  1. 关闭从this返回的游标。 (CursorLoader / LoaderManager会自动为您执行此操作,但如果您在LoaderManager框架之外进行直接查询,或者您已实现自定义CursorLoader / AsyncTaskLoader子类,则必须确保清理游标正常。)
  2. 以线程安全的方式实现ContentProvider。 (最简单的方法是确保数据库访问方法包含在 synchronized 块中。)

答案 2 :(得分:13)

我已关注 Mannaz的回答,并看到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);
    }
}

答案 3 :(得分:7)

如果您希望数据库自动关闭,则可以在打开时提供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();
        }
    }
}

答案 4 :(得分:1)

完成后关闭它,最好是在最后一个块中关闭它,这样你就可以确保它发生了。我知道这听起来有点陈旧而且袖手旁观,但它确实是我所知道的唯一答案。如果您打开数据库并执行操作,请在完成该操作后将其关闭,除非您知道将再次需要它(在这种情况下,一旦不再需要它就一定要关闭它)。

答案 5 :(得分:0)

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