在ContentProvider中关闭数据库


73

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

现在,我可以看到SQLiteOpenHelper具有close()方法。我知道让空闲数据库处于打开状态是一种不好的做法,并且可能导致内存泄漏和其他问题(除非讨论的方向正确)。如果我在Activity中使用该类,则只需在onDestroy()方法中调用close(),但据我所知,ContentProvider与活动没有相同的生命周期。NotePad的代码似乎从未调用过close(),因此我想假设它是由SQLiteOpenHelper或其他难题解决的,但是我真的很想知道。我也不是很信任示例代码。

问题摘要:我们应该什么时候关闭提供程序中的数据库?


11
黛安·哈克伯恩(Dianne Hackborn)表示,无需关闭db
bigstones 2012年

1
这是此线程上最重要的信息。我回答了。
菲利普,2012年

Answers:


95

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

内容提供者是在创建其托管过程时创建的,并且在该过程中一直存在,因此无需关闭数据库-当内核清理进程的资源时,它将作为内核的一部分被关闭。进程被杀死。

感谢@bigstones指出这一点。


5
谢谢。顺便说一句,他们(Android团队)应该在准则或至少在示例代码中对此“简单”内容进行评论,而不是让编码人员在网上搜索它。

16
不要让我着手解决缺失的问题:)
philipp

3
好的,但是关闭功能是做什么的。代码是:public void shutdown(){Log.w(TAG,“实现ContentProvider shutdown()以确保所有数据库” +“连接均正常关闭”);}
ata 2014年

shutdown()在进行Reboelectric ContentProvider单元测试时,您将需要关闭DB 。
Jongz Puangput '19

21

这个问题有点老了,但仍然很有意义。请注意,如果您以“现代”方式进行操作(例如,使用LoaderManager并创建CursorLoaders在后台线程中查询ContentProvider),请确保不要在ContentProvider实现中调用db.close()。当CursorLoader / AsyncTaskLoader尝试在后台线程中访问ContentProvider时,我遇到了各种崩溃,这些崩溃通过删除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. 关闭从ContentProvider.query()返回的游标。(CursorLoader / LoaderManager会自动为您执行此操作,但是如果您是在LoaderManager框架之外进行直接查询,或者已实现了自定义的CursorLoader / AsyncTaskLoader子类,则必须确保清理游标正确地。)
  2. 以线程安全的方式实现ContentProvider。(最简单的方法是确保数据库访问方法包装在一个同步块中。)

13

Ive跟随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);
    }
}

1
当人们花时间回答/更新较旧的问题时,我会喜欢它。谢谢Pleerock!看来我需要重新审视一下……
SilithCrowe 2012年

YW。顺便说一下,我在LeaklessCursor中发现了一些“泄漏”。使用内容提供程序时,此方法无效。例如,当您更新数据库中的数据时,您的光标也会被更新。因此,此光标将被关闭,新的光标将被打开。当我们的光标关闭时,我们的数据库也将关闭。可能会导致错误。例如:ContentProvider(打开数据库)->查询(用于游标,使用db)->更新(任何数据,使用db)->通知->关闭旧的游标(也关闭db)->创建新的游标(查询,使用db)和POOOOW错误-> db已关闭,无法打开新的游标
pleerock 2012年

如果getDatabase()调用可以返回null,则Log.d(...)可能会崩溃,我进行了将Log移至if语句的编辑,并将getDatabase()放入引用中,因为您已使用它3次。您可以在if语句之外添加一个Log,以使您知道应该“应该”关闭了一个数据库,而不管getDatabase()是否为null(如果有用)。
Dandre Allison

注意。在ContentProvider中关闭数据库的想法不是很好。如果可以,请避免这样做。“关闭泄漏的游标”可能会导致许多其他泄漏。内容提供程序用于数据库查询,因此它始终使用数据库。即使您已经完成查询,内容提供者(和数据库实例)也可以依赖于其他类
pleerock 2012年

谢谢,您救了我的一天!
Gordon Freeman

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

1
其他注意事项:请注意Pleerock的答案,因为它以小而重要的方式更新了此答案。顺便说一下,这是一个了不起的解决方案-当我看到良好使用设计模式时,总是会有些头晕。:P
SilithCrowe'2

1

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


2
我们知道是否再次需要它的唯一方法是从提供者外部(在使用它的代码中)。我认为在提供程序内部,每次在SQLiteOpenHelper上调用getWriteableDatabase()或getReadableDatabase()时都将访问数据库。根据您的建议,在每个调用这些方法的方法之后,我都应该添加一个close()吗?似乎如果一个接一个地运行多个查询,那么将会有大量的数据库打开和关闭。我不确定,但是我想这会影响性能。
SilithCrowe 2011年

0

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


当然,如果您使用的是sql lite,则可以在获得结果后关闭连接。(再次确保使用生命周期游标使用startmanagingcursor处理光标的生命周期。–
uncaught_exceptions
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.