Закрытие базы данных в ContentProvider - PullRequest
73 голосов
/ 28 декабря 2010

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

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

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

Ответы [ 6 ]

90 голосов
/ 03 октября 2012

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

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

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

21 голосов
/ 09 августа 2012

Этот вопрос немного стар, но все еще актуален. Обратите внимание, что если вы делаете вещи «современным» способом (например, используя 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 и убедитесь, что вы не закрываете базу данных преждевременно. Согласно this , ContentProvider будет очищен автоматически, когда процесс все равно будет остановлен, поэтому вам не нужно закрывать его базу данных заранее.

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

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

Я последовал за ответом Манназа и увидел, что конструктор 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);
    }
}
7 голосов
/ 26 января 2011

Если вы хотите, чтобы база данных автоматически закрывалась, вы можете указать 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 голос
/ 04 января 2011

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

0 голосов
/ 20 января 2011

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

...