Android SQLite DB, kiedy zamknąć


98

Pracuję z bazą danych SQLite na Androidzie. Mój menedżer bazy danych jest singletonem i teraz otwiera połączenie z bazą danych po jej zainicjowaniu. Czy pozostawianie bazy danych otwartej przez cały czas jest bezpieczne, aby gdy ktoś dzwoni do mojej klasy w celu pracy z bazą danych, jest ona już otwarta? A może powinienem otworzyć i zamknąć bazę danych przed i po każdym dostępie. Czy jest coś złego w pozostawieniu go otwartego przez cały czas?

Dzięki!

Odpowiedzi:


60

Pozostawiłbym go otwarty przez cały czas i zamknąłbym go w jakiejś metodzie cyklu życia, takiej jak onStoplub onDestroy. w ten sposób możesz łatwo sprawdzić, czy baza danych jest już używana, dzwoniąc isDbLockedByCurrentThreadlub isDbLockedByOtherThreadspojedynczoSQLiteDatabase obiektu za każdym razem przed użyciem. zapobiegnie to wielu manipulacjom w bazie danych i uchroni aplikację przed potencjalną awarią

więc w swoim singletonie możesz mieć taką metodę, aby pobrać pojedynczy SQLiteOpenHelperobiekt:

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

więc kiedy chcesz użyć swojego otwartego obiektu pomocniczego, wywołaj tę metodę pobierającą (upewnij się, że jest podzielona na wątki)

inną metodą w twoim singletonie może być (nazywana KAŻDYM RAZEM zanim spróbujesz wywołać powyższy getter):

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
    }
}

możesz chcieć zamknąć bazę danych również w singletonie:

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

jeśli użytkownicy Twojej aplikacji mają możliwość bardzo szybkiego tworzenia wielu interakcji z bazą danych, powinieneś użyć czegoś, co pokazałem powyżej. ale jeśli interakcje z bazą danych są minimalne, nie martwiłbym się tym i po prostu tworzyłem i zamykałem bazę danych za każdym razem.


Mógłbym przyspieszyć dostęp do bazy danych o współczynnik 2, z tym podejściem (Open Database zachować), dzięki
teh.fonsi

2
Spinning to bardzo zła technika. Zobacz moją odpowiedź poniżej.
mixel

1
@mixel dobra uwaga. Wydaje mi się, że opublikowałem tę odpowiedź przed udostępnieniem API 16, ale mogę się mylić
James

1
@binnyb Myślę, że lepiej zaktualizować swoją odpowiedź, aby nie wprowadzała ludzi w błąd.
mixel

3
WARN isDbLockedByOtherThreads () jest Depricated i zwraca wartość false od API 16. Również sprawdzenie isDbLockedByCurrentThread () da nieskończoną pętlę, ponieważ zatrzymuje bieżący wątek i nic nie może „odblokować DB”, aby ta metoda zwróciła wartość true.
matreshkin

20

Na razie nie ma potrzeby sprawdzania, czy baza danych jest zablokowana przez inny wątek. Używając singletona SQLiteOpenHelper w każdym wątku, jesteś bezpieczny. Z isDbLockedByCurrentThreaddokumentacji:

Nazwa tej metody pochodzi z czasów, gdy posiadanie aktywnego połączenia z bazą danych oznaczało, że wątek utrzymywał rzeczywistą blokadę bazy danych. Obecnie nie istnieje już prawdziwa „blokada bazy danych”, chociaż wątki mogą blokować się, jeśli nie mogą uzyskać połączenia z bazą danych w celu wykonania określonej operacji.

isDbLockedByOtherThreads jest przestarzałe od 16 poziomu interfejsu API.


Nie ma sensu używać jednej instancji na wątek. SQLiteOpenHelper jest bezpieczny dla wątków. Jest to również bardzo nieefektywne pod względem pamięci. Zamiast tego aplikacja powinna zachowywać pojedyncze wystąpienie SQLiteOpenHelper dla każdej bazy danych. Aby uzyskać lepszą współbieżność, zaleca się używanie funkcji WriteAheadLogging, która zapewnia buforowanie połączeń dla maksymalnie 4 połączeń.
ejboy

@ejboy Miałem to na myśli. „Użyj singleton (= jeden) SQLiteOpenHelper w każdym wątku”, a nie „na wątek”.
mixel

15

Odnośnie pytań:

Mój menedżer bazy danych jest singletonem i teraz otwiera połączenie z bazą danych po jej zainicjowaniu.

Powinniśmy podzielić „otwieranie bazy danych”, „otwieranie połączenia”. SQLiteOpenHelper.getWritableDatabase () daje otwartą bazę danych. Ale nie musimy kontrolować połączeń, ponieważ odbywa się to wewnętrznie.

Czy pozostawianie bazy danych otwartej przez cały czas jest bezpieczne, aby gdy ktoś dzwoni do mojej klasy w celu pracy z bazą danych, jest ona już otwarta?

Tak to jest. Połączenia nie zawieszają się, jeśli transakcje są poprawnie zamknięte. Zauważ, że twoja baza danych zostanie również automatycznie zamknięta, jeśli GC ją sfinalizuje.

A może powinienem otworzyć i zamknąć bazę danych przed i po każdym dostępie.

Zamknięcie instancji SQLiteDatabase nie daje nic wielkiego poza zamykaniem połączeń, ale jest to zła sytuacja programisty, jeśli w tej chwili są jakieś połączenia. Ponadto po SQLiteDatabase.close () SQLiteOpenHelper.getWritableDatabase () zwróci nową instancję.

Czy jest coś złego w pozostawieniu go otwartego przez cały czas?

Nie, nie ma. Zauważ również, że zamknięcie bazy danych w niepowiązanym momencie i wątku, np. W Activity.onStop () może zamknąć aktywne połączenia i pozostawić dane w stanie niespójnym.


Dziękuję, po przeczytaniu twoich słów "po SQLiteDatabase.close (), SQLiteOpenHelper.getWritableDatabase () zwróci nową instancję" Zdałem sobie sprawę, że w końcu mam odpowiedź na stary problem mojej aplikacji. Do problemu, który stał się krytyczny po migracji na Androida 9 (patrz stackoverflow.com/a/54224922/297710 )
yvolk


1

Z punktu widzenia wydajności optymalnym sposobem jest utrzymanie pojedynczego wystąpienia SQLiteOpenHelper na poziomie aplikacji. Otwieranie bazy danych może być kosztowne i jest operacją blokującą, więc nie powinno się tego robić w głównym wątku i / lub w metodach cyklu życia działania.

setIdleConnectionTimeout () (wprowadzona w systemie Android 8.1) może służyć do zwolnienia pamięci RAM, gdy baza danych nie jest używana. Jeśli ustawiony jest limit czasu bezczynności, połączenia z bazą danych zostaną zamknięte po okresie bezczynności, tj. Gdy nie uzyskano dostępu do bazy danych. Połączenia zostaną ponownie otwarte w trybie przezroczystym dla aplikacji, gdy zostanie wykonane nowe zapytanie.

Oprócz tego aplikacja może wywołać funkcję releaseMemory (), gdy przechodzi w tło lub wykryje obciążenie pamięci, np. W onTrimMemory ()



-9

Utwórz własny kontekst aplikacji, a następnie otwórz i zamknij bazę danych z tego miejsca. Ten obiekt ma również metodę OnTerminate (), której można użyć do zamknięcia połączenia. Jeszcze tego nie próbowałem, ale wydaje się, że jest to lepsze podejście.

@binnyb: Nie lubię używać finalize () do zamykania połączenia. Może działać, ale z tego, co rozumiem, pisanie kodu w metodzie finalize () w języku Java to zły pomysł.


Nie chcesz polegać na finalizacji, ponieważ nigdy nie wiesz, kiedy zostanie wywołana. Obiekt może pozostawać w zawieszeniu, dopóki GC nie zdecyduje się go wyczyścić i dopiero wtedy zostanie wywołana finalize (). Dlatego jest bezużyteczny. Prawidłowym sposobem na to jest posiadanie metody, która jest wywoływana, gdy obiekt nie jest już używany (niezależnie od tego, czy został usunięty, czy nie), i właśnie tym jest onTerminate ().
erwan

6
Dokumenty mówią dość wyraźnie, że onTerminatenie zostanie wywołane w środowisku produkcyjnym - developer.android.com/reference/android/app/ ...
Eliezer
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.