Проблема утечки базы данных контент-провайдера Android

Я пишу контент-провайдера для этого приложения, и в моем провайдере контента я открываю соединение с базой данных, запускаю запрос и возвращаю курсор результатов в вызывающую программу. Если я закрою это соединение с базой данных в провайдере, курсор не получит никаких результатов. Если я оставлю его открытым, я получаю ошибки "обнаруженные утечки" в моем журнале DDMS. Что мне здесь не хватает? Какой чистый и правильный способ вернуть курсор результатов базы данных?

Ответ 1

Вы не пропустите что-либо AFAIK. Android отсутствует onDestroy() (или эквивалент) для ContentProvider. В исходном коде в этой области нет ничего, что предполагает, что есть какой-то onDestroy(), который просто не отображается в SDK.

Если вы посмотрите на исходный код для AlarmProvider и LauncherProvider, они даже создают объекты базы данных для каждого API-вызова (например, каждый раз, когда они получают insert()), они открывают дескриптор записи для записи, который они никогда не закрываются).

Ответ 2

Один из моих контент-провайдеров управляет несколькими базами данных, той же схемой с разными наборами данных. Чтобы предотвратить пропущение IllegalStateException, когда сбор мусора обнаружил, что в моем контент-провайдере была открытая база данных, которая больше не ссылалась на нее, даже несмотря на то, что SQLiteCursor оставил мне несколько вариантов:

1) Оставьте объект SQLiteDatabase открытым и поместите его в коллекцию, чтобы больше не использовать.

2) Оставьте объект SQLiteDatabase открытым и создайте кэш, который позволит мне повторно использовать объект базы данных при обращении к той же базе данных.

3) Закройте базу данных, когда курсор закрыт.

Решение 1 противоречит моему лучшему суждению. Решение - это еще одна форма утечки ресурсов; его единственная экономическая благодать заключается в том, что она не нарушает систему. Я решил этот выбор немедленно.

Решение 2 было моей идеей лучшего решения. Он сохранил ресурсы и одновременно сократил время выполнения, не открыв соединения с базой данных. Нижняя сторона этого решения заключалась в том, что мне пришлось бы писать кеш, и это увеличило бы размер приложения. Размер действительно не был проблемой, но время было написано. Я передал это решение в настоящее время и могу вернуться к нему позже.

Решение 3 - это тот, который решил пойти. Во-первых, я думал, что это будет просто сделать; в действии все, что мне нужно было сделать, это переустановить курсор, возвращаемый моим Content Provider обратно в SQLiteCursor. Затем я мог бы вызвать его метод getDatabase() и вызвать close() в базе данных.

Это невозможно. Оказывается, что курсор, возвращаемый поставщиком контента, находится в классе-оболочке, который предотвращает прямой доступ к фактическому объекту Cursor. Метод делегата-делегата оболочки вызывает его получение в Cursor. Нет шансов вернуть курсор обратно к его производному типу.

Итак, вместо того, чтобы возлагать ответственность за закрытие базы данных на активность, я пошел другим и более простым путем. Я получил свой собственный класс Cursor, расширив класс SQLiteCursor. Я добавил два члена данных в свой производный класс; один для ссылки на базу данных, а другой - идентификатор для использования в отладке. Класс ctor имел ту же подпись, что и SQLiteCursor ctor, с дополнительным параметром, добавленным в конец для установки значения ID. Ctor установил член данных базы данных, чтобы убедиться, что что-то ссылалось на базу данных, если сборка мусора произошла до того, как курсор был закрыт.

Я перепробовал метод close(), чтобы он как закрыл курсор [super.close()], так и закрыл базу данных, если ссылка не была нулевой.

Я также переопределил метод toString(), чтобы идентификационный номер был добавлен к строке. Это позволило мне отслеживать, какие курсоры были открыты и какие из них были открыты и закрыты в файле журнала.

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

Я также создал класс, который реализовал интерфейс SQLiteDatabase.CursorFactory. Он создал мой новый производный класс курсора и управлял значением идентификатора, переданным каждому из них.

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

Ответ 3

Это прекрасно, оставив соединение с базой данных открытым в течение всего времени выполнения вашего приложения, вам просто нужно убедиться, что вы закроете курсор после того, как вы закончите с ним.

Я предполагаю, что вы запрашиваете и используете курсоры в Activity? если это так, убедитесь, что вы закрываете курсоры, вызывая метод cursor.close();, я замечаю, что вы не закрываете курсоры в Activity, а затем переходите на другое действие, которое вы получите эти сообщения об утечке при запуске другого запроса.

Я считаю, что лучше всего переопределить метод onDestroy в вашей деятельности и закрыть все курсоры в нем.