2010-12-28 2 views
71

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

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

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

+11

Dianne Hackborn сказал, что [нет необходимости закрывать db] (http://groups.google.com/d/msg/android-developers/NwDRpHUXt0U/jIam4Q8-cqQJ). – bigstones

+1

Это самая важная информация в этой теме. Я дал ему ответ. – philipp

ответ

88

According to Dianne Hackborn (рамочная инженер Android) нет необходимости закрывать базу данных в контент-провайдера.

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

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

+5

Спасибо. Btw, они (команда Android) должны прокомментировать эту «простую» вещь в [guideline] (http://developer.android.com/guide/topics/providers/content-provider-creating.html) или, по крайней мере, в образце кода, а не позволяя кодерм искать его в сети. –

+14

Не поймите меня, что не хватает :) – philipp

+3

Хорошо, но что делает функция выключения. Код: public void shutdown() { Log.w (TAG, «реализовать ContentProvider shutdown(), чтобы убедиться, что все базы данных« + «соединения изящно завершены»); } – ata

1

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

+2

Единственный способ узнать, понадобится ли нам это снова, - это не за пределами провайдера (в коде, который его использует). Я думаю, что внутри провайдера доступ к базе данных происходит каждый раз, когда на SQLiteOpenHelper вызывается getWriteableDatabase() или getReadableDatabase(). Основываясь на своем предположении, следует ли добавлять close() впоследствии в каждый метод, где они вызываются? Кажется, что если несколько запросов запускались один за другим, тогда было бы много открытия и закрытия базы данных. Я не уверен, но я бы предположил, что это повлияет на производительность. – SilithCrowe

0

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

+0

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

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

Примечание для других: обратите внимание на ответ Плейрока, так как он обновляет этот ответ небольшим, но важным способом. Это потрясающее решение, кстати - я всегда получаю немного головокружение, когда я вижу хорошее использование шаблонов дизайна.: P – SilithCrowe

13

Ive следовать Ответы Mannaz's ответили, что конструктор 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

+0

YW. Я обнаружил некоторую «утечку» в нашем LeaklessCursor. Это не эффективно, когда вы используете контент-провайдеров. Например, когда вы обновляете данные в базе данных, ваш курсор также будет обновлен. Таким образом, этот курсор будет закрыт и новый курсор будет открыт. Когда наш курсор будет закрыт, наша база данных также будет закрыта. Это может привести к ошибке.Например: ContentProvider (открывает базу данных) -> запрос (для курсора, использует db) -> обновление (любые данные, использует db) -> уведомляет-> закрывает старый курсор (также закрывает db) -> создает новый курсор (запрос, использует db), а ошибка POOOOW -> db была закрыта, не может открыть новый курсор – pleerock

+0

, если вызов getDatabase() может возвращать null, тогда может произойти сбой Log.d (...), я введу редактирование, которое перемещает Log в if-statement и помещает getDatabase() в ссылку, так как вы используете его 3 раза. Вы можете добавить журнал вне оператора if, который позволяет вам знать, что вы должны «закрыть» базу данных, независимо от того, была ли getDatabase() пустой, если это полезно. –

21

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

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

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