БД Android SQLite Коли закривати


96

Я працюю з базою даних SQLite на Android. Мій менеджер баз даних є одностороннім і зараз відкриває підключення до бази даних, коли вона ініціалізується. Безпечно залишати базу даних відкритою весь час, щоб коли хтось зателефонував моєму класу для роботи з базою даних, вона вже відкрита? Або я повинен відкривати та закривати базу даних до і після кожного необхідного доступу. Чи є якась шкода, якщо просто весь час залишати його відкритим?

Дякую!

Відповіді:


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();
}

якщо користувачі вашого додатку мають можливість створити багато взаємодій з базами даних дуже швидко, вам слід використовувати щось на зразок того, що я продемонстрував вище. але якщо існує мінімальна взаємодія з базою даних, я б не хвилювався про це, а просто створював і закривав базу даних щоразу.


Я міг би пришвидшити доступ до бази даних в два рази за допомогою цього підходу (тримати базу даних відкритою), дякую
teh.fonsi

2
Спінінг - дуже погана техніка. Дивіться мою відповідь нижче.
mixel

1
@mixel хороший момент. Я вважаю, що я опублікував цю відповідь до того, як API 16 був доступний, але я можу помилитися
Джеймс

1
@binnyb Я думаю, що краще оновити свою відповідь, щоб це не вводило людей в оману.
mixel

3
WARN isDbLockedByOtherThreads () знецінюється і повертає false, оскільки API 16. Також перевірка isDbLockedByCurrentThread () дасть нескінченний цикл, оскільки він зупиняє поточний потік і ніщо не може "розблокувати БД", так що цей метод повертає істину.
матрешкін

20

На даний момент немає необхідності перевіряти, чи заблокована база даних іншим потоком. Поки ви використовуєте односторонній SQLiteOpenHelper у кожному потоці, ви перебуваєте в безпеці. З isDbLockedByCurrentThreadдокументації:

Назва цього методу походить з часів, коли наявність активного з'єднання з базою даних означало, що потік утримував фактичну блокування бази даних. На сьогоднішній день вже не існує справжнього "блокування бази даних", хоча потоки можуть блокувати, якщо вони не можуть отримати з'єднання з базою даних для виконання певної операції.

isDbLockedByOtherThreads застаріло з API рівня 16.


Немає сенсу використовувати один екземпляр для потоку. SQLiteOpenHelper є потокобезпечним. Це також дуже неефективно для пам'яті. Натомість додаток повинен зберігати один екземпляр SQLiteOpenHelper для кожної бази даних. Для кращої паралельності рекомендується використовувати WriteAheadLogging, який забезпечує об'єднання з'єднань до 4 з'єднань.
ejboy

@ejboy Я це мав на увазі. Msgstr "Використовувати одиночний (= один) 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 ()



-9

Створіть власний контекст програми, а потім відкрийте та закрийте базу даних звідти. Цей об'єкт також має метод OnTerminate (), який ви могли б використати для закриття з'єднання. Я ще не пробував, але, схоже, кращий підхід.

@binnyb: Мені не подобається використовувати finalize () для закриття з'єднання. Може спрацювати, але з того, що я розумію, написання коду в методі Java finalize () - погана ідея.


Ви не хочете покладатися на фіналізм, тому що ніколи не знаєте, коли його буде викликано. Об'єкт може залишатися в невизначеному стані, доки GC не вирішить його очистити, і лише тоді буде викликано finalize (). Тому це марно. Правильний спосіб зробити це - мати метод, який викликається, коли об’єкт більше не використовується (незалежно від того, збирається сміття чи ні), і саме це і є onTerminate ().
erwan

5
Документи досить чітко кажуть, що onTerminateїх не буде викликано у виробничому середовищі - developer.android.com/reference/android/app/…
Eliezer,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.