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

72

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

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

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

  • 11
    Дайан Хэкборн сказала, что нет необходимости закрывать БД .
  • 1
    Это самая важная информация в этой теме. Я сделал это ответом.
Теги:
android-contentprovider

6 ответов

93
Лучший ответ

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

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

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

  • 5
    Благодарю. Кстати, они (команда Android) должны прокомментировать эту «простую» вещь в руководстве или, по крайней мере, в примере кода, а не позволять кодировщикам искать ее в сети.
  • 15
    не начинайте меня с того, что не хватает :)
Показать ещё 1 комментарий
22

Этот вопрос немного стар, но по-прежнему весьма уместен. Обратите внимание, что если вы делаете что-то "современным" способом (например, используя 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 поточно-безопасным способом. (Самый простой способ сделать это - убедиться, что методы доступа к базе данных завернуты в синхронизированный блок.)
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
    Мне нравится, когда люди находят время, чтобы ответить / обновить старые вопросы. Спасибо, Плерок! Похоже, мне нужно вернуться к этому ...
  • 0
    YW. Кстати, я обнаружил некоторую «утечку» в нашем LeaklessCursor. Это не эффективно, когда вы используете контент-провайдеров. Например, когда вы обновляете данные в базе данных, ваш курсор тоже будет обновляться. Таким образом, этот курсор будет закрыт, и новый курсор будет открыт. Когда наш курсор будет закрыт, наша база данных тоже будет закрыта. Это может вызвать ошибку. Например: ContentProvider (открывает базу данных) -> запрос (для курсора, использует БД) -> обновить (любые данные, использует БД) -> уведомляет-> закрывает старый курсор (также закрывает БД) -> создает новый курсор (запрос, использует db) и ошибку POOOOW -> db была закрыта, не может открыть новый курсор
Показать ещё 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();
        }
    }
}
  • 1
    Примечание для других: Обратите внимание на ответ Плерока, так как он обновляет этот ответ небольшим, но важным способом. Кстати, это потрясающее решение - я всегда чувствую легкое головокружение, когда вижу хорошее использование шаблонов дизайна. :П
1

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

  • 2
    Единственный способ узнать, понадобится ли он нам снова, это за пределами провайдера (в коде, который его использует). Я думаю, что внутри провайдера доступ к базе данных осуществляется каждый раз, когда getWriteableDatabase () или getReadableDatabase () вызывается в SQLiteOpenHelper. Исходя из вашего предложения, я должен добавить close () впоследствии в каждом методе, где они вызываются? Похоже, что если бы несколько запросов выполнялись один за другим, тогда было бы много операций по открытию и закрытию базы данных. Я не уверен, но я думаю, что это повлияет на производительность.
0

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

  • 0
    Конечно, если вы используете sql lite, вы можете закрыть соединение после получения результатов. (Снова убедитесь, что жизненный цикл курсора обрабатывается активностью, используя startmanagingcursor.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню