Android SQLite DB Когда закрывать

96

Я работаю с базой данных SQLite на android. Мой менеджер баз данных является одноэлементным и прямо сейчас открывает соединение с базой данных при ее инициализации. Безопасно оставлять базу данных открытой все время, чтобы, когда кто-то вызывает мой класс для работы с базой данных, она уже открыта? Или мне следует открывать и закрывать базу данных до и после каждого доступа. Есть ли вред просто оставлять его открытым все время?

Спасибо!

W.donahue
источник

Ответы:

60

Я бы держал его открытым все время и закрывал каким-нибудь методом жизненного цикла, например onStopили onDestroy. Таким образом, вы можете легко проверить , если база данных уже используется по телефону isDbLockedByCurrentThreadили isDbLockedByOtherThreadsна одном SQLiteDatabaseобъекте каждый раз , прежде чем использовать его. это предотвратит множественные манипуляции с базой данных и спасет ваше приложение от потенциального сбоя

поэтому в вашем синглтоне у вас может быть такой метод для получения вашего единственного SQLiteOpenHelperобъекта:

private SQLiteDatabase db;
private MyDBOpenHelper mySingletonHelperField;
public MyDBOpenHelper getDbHelper() {
    db = mySingletonHelperField.getDatabase();//returns the already created database object in my MyDBOpenHelper class(which extends `SQLiteOpenHelper`)
    while(db.isDbLockedByCurrentThread() || db.isDbLockedByOtherThreads()) {
        //db is locked, keep looping
    }
    return mySingletonHelperField;
}

поэтому всякий раз, когда вы хотите использовать свой открытый вспомогательный объект, вызывайте этот метод получения (убедитесь, что он многопоточен)

другой метод в вашем синглтоне может быть (вызывается КАЖДЫЙ РАЗ, прежде чем вы попытаетесь вызвать метод получения выше):

public void setDbHelper(MyDBOpenHelper mySingletonHelperField) {
    if(null == this.mySingletonHelperField) {
        this.mySingletonHelperField = mySingletonHelperField;
        this.mySingletonHelperField.setDb(this.mySingletonHelperField.getWritableDatabase());//creates and sets the database object in the MyDBOpenHelper class
    }
}

вы также можете закрыть базу данных в синглтоне:

public void finalize() throws Throwable {
    if(null != mySingletonHelperField)
        mySingletonHelperField.close();
    if(null != db)
        db.close();
    super.finalize();
}

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

Джеймс
источник
При таком подходе я мог бы ускорить доступ к базе данных в 2 раза (оставьте базу данных открытой), спасибо
teh.fonsi
2
Вращение - очень плохая техника. Смотрите мой ответ ниже.
mixel
1
@mixel хороший момент. Я считаю, что опубликовал этот ответ до того, как API 16 был доступен, но я мог ошибаться
Джеймс
1
@binnyb Я думаю, что лучше обновить свой ответ, чтобы он не вводил людей в заблуждение.
mixel
3
ПРЕДУПРЕЖДЕНИЕ isDbLockedByOtherThreads () является Depricated и возвращает false с API 16. Также проверка isDbLockedByCurrentThread () даст бесконечный цикл, потому что он останавливает текущий поток и ничто не может «разблокировать БД», так что этот метод возвращает true.
Матрешкин
20

На данный момент нет необходимости проверять, заблокирована ли база данных другим потоком. Хотя вы используете одноэлементный SQLiteOpenHelper в каждом потоке, вы в безопасности. Из isDbLockedByCurrentThreadдокументации:

Название этого метода происходит от того времени, когда активное соединение с базой данных означало, что поток удерживал фактическую блокировку базы данных. В настоящее время больше не существует настоящей «блокировки базы данных», хотя потоки могут блокироваться, если они не могут получить соединение с базой данных для выполнения определенной операции.

isDbLockedByOtherThreads устарело с API уровня 16.

миксель
источник
Нет смысла использовать один экземпляр на поток. SQLiteOpenHelper потокобезопасен. Это также очень неэффективно с памятью. Вместо этого приложение должно хранить один экземпляр SQLiteOpenHelper для каждой базы данных. Для лучшего параллелизма рекомендуется использовать WriteAheadLogging, который обеспечивает объединение до 4 соединений.
ejboy
@ejboy Я имел в виду именно это. «Использовать одноэлементный (= один) SQLiteOpenHelper в каждом потоке», а не «для каждого потока».
mixel
15

По вопросам:

Мой менеджер баз данных является одноэлементным и прямо сейчас открывает соединение с базой данных при ее инициализации.

Мы должны разделить «открытие БД», «открытие соединения». SQLiteOpenHelper.getWritableDatabase () предоставляет открытую БД. Но нам не нужно контролировать соединения, поскольку это делается внутри компании.

Безопасно оставлять базу данных открытой все время, чтобы, когда кто-то вызывает мой класс для работы с базой данных, она уже открыта?

Да, это так. Соединения не зависают, если транзакции правильно закрыты. Обратите внимание, что ваша БД также будет автоматически закрыта, если GC завершит ее.

Или мне следует открывать и закрывать базу данных до и после каждого доступа.

Закрытие экземпляра SQLiteDatabase не дает ничего особенного, кроме закрытия подключений, но это плохо для разработчика, если в данный момент есть какие-то подключения. Кроме того, после SQLiteDatabase.close () SQLiteOpenHelper.getWritableDatabase () вернет новый экземпляр.

Есть ли вред просто оставлять его открытым все время?

Нет, нет. Также обратите внимание, что закрытие БД в несвязанный момент и поток, например, в Activity.onStop (), может закрыть активные соединения и оставить данные в несогласованном состоянии.

Матрешкин
источник
Спасибо, прочитав ваши слова «после SQLiteDatabase.close () SQLiteOpenHelper.getWritableDatabase () вернет новый экземпляр», я понял, что наконец-то у меня есть ответ на старую проблему моего приложения. К проблеме, которая стала критической после перехода на Android 9 (см stackoverflow.com/a/54224922/297710 )
yvolk
1

В Android 8.1 есть SQLiteOpenHelper.setIdleConnectionTimeout(long)метод, который:

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

https://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html#setIdleConnectionTimeout(long)

отметка
источник
3
В наши дни это УСТАРЕЛО.
matreshkin
1

С точки зрения производительности оптимальный способ - сохранить единственный экземпляр SQLiteOpenHelper на уровне приложения. Открытие базы данных может быть дорогостоящим и является блокирующей операцией, поэтому ее не следует выполнять в основном потоке и / или в методах жизненного цикла активности.

Метод setIdleConnectionTimeout () (представленный в Android 8.1) можно использовать для освобождения оперативной памяти, когда база данных не используется. Если установлен тайм-аут простоя, соединение (я) с базой данных будет закрыто после периода бездействия, т. Е. Когда к базе данных не было доступа. Соединения будут повторно открываться прозрачно для приложения при выполнении нового запроса.

Кроме того, приложение может вызывать releaseMemory (), когда оно переходит в фоновый режим или обнаруживает нехватку памяти, например, в onTrimMemory ().

Ejboy
источник
0

Вы также можете использовать ContentProvider. Он сделает это за вас.

Грегори Буйко
источник
-9

Создайте свой собственный контекст приложения, затем откройте и закройте базу данных оттуда. Этот объект также имеет метод OnTerminate (), который можно использовать для закрытия соединения. Я еще не пробовал, но кажется, что это лучший подход.

@binnyb: Мне не нравится использовать finalize () для закрытия соединения. Может работать, но из того, что я понимаю, писать код в методе Java finalize () - плохая идея.

Leander
источник
Вы не хотите полагаться на finalize, потому что никогда не знаете, когда он будет вызван. Объект может оставаться в подвешенном состоянии до тех пор, пока сборщик мусора не решит его очистить, и только после этого будет вызвана finalize (). Вот почему это бесполезно. Правильный способ сделать это - иметь метод, который вызывается, когда объект больше не используется (независимо от того, сборщик мусора или нет), и это именно то, что есть onTerminate ().
erwan
5
Документы довольно четко говорят, что onTerminateне будет вызываться в производственной среде - developer.android.com/reference/android/app/…
Элиэзер