Закрытие базы данных в ContentProvider
на этой неделе я узнал все о ContentProvider и использовании класса SQLiteOpenHelper для управления созданием и обновлением базы данных внутри поставщика. В частности, я читал пример Блокнота из каталога образцов sdk.
теперь я вижу, что SQLiteOpenHelper имеет метод close (). Я знаю, что оставлять незанятые базы данных открытыми-это плохая практика и может привести к утечкам памяти и тому подобное (если это обсуждение не возглавляется справа направление.) Если бы я использовал класс в Activity, то я бы просто вызвал close() в методе onDestroy (), но, насколько я знаю, ContentProvider не имеет того же жизненного цикла, что и activities. Код для Блокнота никогда не вызывает close (), поэтому я хотел бы предположить, что он обрабатывается SQLiteOpenHelper или какой-либо другой частью головоломки, но я действительно хотел бы знать наверняка. Я тоже не очень доверяю образцу кода...
резюме вопрос: когда должны ли мы закрыть базу данных в провайдере, если вообще?
6 ответов:
по данным с Dianne Hackborn (Android framework engineer) нет необходимости закрывать базу данных в контент-провайдере.
контент-провайдер создается при создании его хостинга, и остается вокруг до тех пор, как процесс делает, так что нет необходимости закройте базу данных -- она будет закрыта как часть ядра очистка ресурсов процесса при его завершении.
спасибо @bigstones за указывая на это.
этот вопрос немного устарел, но все еще довольно актуален. Обратите внимание, что если вы делаете вещи "современным" способом (например, используя LoaderManager и создавая CursorLoaders для запроса ContentProvider в фоновом потоке), убедитесь, что вы не вызываете db.закрыть() в вашей реализации ContentProvider. Я получал всевозможные сбои, связанные с CursorLoader/AsyncTaskLoader, когда он пытался получить доступ к ContentProvider в фоновом потоке, которые были разрешены путем удаления децибел.закрыть () вызовы.
Так что если вы столкнулись с авариями, которые выглядят так (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.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.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 в потокобезопасным способом. (Самый простой способ сделать это-убедиться, что ваши методы доступа к базе данных завернуты в синхронизироваться блок.)
Я следую Манназ-это ответь и увидишь что
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); } }
Если вы хотите, чтобы ваша база данных закрывалась автоматически, вы можете предоставить
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(); } } }
закройте его, когда вы закончите с ним, предпочтительно в блоке finally, чтобы вы могли убедиться, что это произойдет. Я знаю, что это звучит немного банально и неуместно, но это действительно единственный ответ, который я знаю. Если вы открываете базу данных и выполняете действие, закройте ее, когда вы закончите с этим действием, если вы не знаете, что это будет нужно снова (в этом случае обязательно закройте ее, когда она больше не нужна).
Если вы используете ваш контент-провайдер в рамках деятельности, то я не считаю, что вы должны поддерживать соединение с контент-провайдером. Вы можете просто управлять объектом курсора, возвращенным с помощью startManagingCursor. В метод onPause активности, вы можете выпустить контент-провайдера. ( вы можете перезагрузить его в onResume). Предполагая, что жизненный цикл деятельности обычно будет ограничен, этого будет достаточно. (По крайней мере, по мне ;))