Закрытие базы данных в ContentProvider

На этой неделе я изучил все о ContentProvider и использовании класса SQLiteOpenHelper для управления созданием и обновлением базы данных внутри поставщика. В частности, я читал пример NotePad из каталога образцов sdk.

Теперь я вижу, что SQLiteOpenHelper имеет метод close(). Я знаю, что отказ от открытых баз данных - это плохая практика и может вызвать утечку памяти и многое другое (если это обсуждение не направлено). Если бы я использовал класс в Activity, тогда я бы просто вызвал метод close() в методе onDestroy(), но, насколько я знаю, ContentProvider не имеет того же жизненного цикла, что и действия. Код для NotePad никогда не вызывает вызов close(), поэтому я хотел бы предположить, что он обрабатывается SQLiteOpenHelper или какой-либо другой частью головоломки, но я действительно хотел бы знать наверняка. Я действительно не очень доверяю образцу кода, либо...

Резюме вопроса: когда нужно закрыть базу данных у поставщика, если вообще?

Ответ 1

По словам Дайанн Хэкборн (разработчик флеш-памяти Android), нет необходимости закрывать базу данных в поставщике контента.

Поставщик контента создается, когда его хостинг-процесс создается, и остается вокруг до тех пор, пока процесс выполняется, поэтому нет необходимости закрыть базу данных - она ​​будет закрыта как часть ядра очистка ресурсов процесса, когда процесс убит.

Спасибо @bigstones за указание на это.

Ответ 2

Этот вопрос немного стар, но по-прежнему весьма уместен. Обратите внимание, что если вы делаете что-то "современным" способом (например, используя LoaderManager и создавая CursorLoaders для запроса ContentProvider в фоновом потоке), убедитесь, что вы не вызываете db.close() в вашей реализации ContentProvider. Я получал всевозможные сбои, связанные с 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 будет автоматически очищаться, когда процесс будет убит, так что вам не нужно закрывать его базу данных раньше времени.

Тем не менее, убедитесь, что вы все еще правильно:

  • Закрытие ваших курсоров, которые возвращаются из ContentProvider.query(). (CursorLoader/LoaderManager делает это автоматически для вас, но если вы выполняете прямые запросы вне рамки LoaderManager или вы внедрили собственный подкласс CursorLoader/AsyncTaskLoader, вам нужно убедиться, что вы очищаете свои курсоры должным образом).
  • Реализация вашего ContentProvider поточно-безопасным способом. (Самый простой способ сделать это - убедиться, что методы доступа к базе данных завернуты в синхронизированный блок.)

Ответ 3

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

Ответ 4

Если вы хотите, чтобы ваша база данных автоматически закрывалась, вы можете предоставить 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();
        }
    }
}

Ответ 5

Закройте его, когда закончите с ним, желательно в блоке finally, чтобы вы могли убедиться, что это произойдет. Я знаю, что это звучит немного банально и безошибочно, но это действительно единственный ответ, о котором я знаю. Если вы откроете базу данных и выполните действие, закройте ее, когда вы закончите это действие, если вы не знаете, что это будет необходимо снова (в этом случае обязательно закройте его, как только он больше не понадобится).

Ответ 6

Если вы используете своего поставщика контента в рамках действия, я не считаю, что вам необходимо поддерживать соединение поставщика контента. Вы можете просто управлять объектом курсора, который был возвращен с помощью startManagingCursor. В методе onPause вы можете освободить поставщика контента. (вы можете перезагрузить его в onResume). Предполагая, что жизненный цикл активности обычно будет ограничен, этого будет достаточно. (По крайней мере, по мне;))