2011-04-07 12 views
11

W mojej aplikacji muszę wdrożyć niektóre usługi interfejsu użytkownika i synchronizacji. Działa w tle i aktualizuje dane. Usługa synchronizacji nie jest bardzo prosta, korzysta z wielowątkowości.Co powiesz na wielowątkowość w Android SQLite?

Tak, oto moja historia: Kiedy zacząłem rozwijać tę aplikację, nie wiedziałem nic o sqlite, więc po prostu nie używałem żadnej synchronizacji wątków w Javie. Wynik: Mam wiele wyjątków, takich jak "SQLiteException: baza danych jest zablokowana: BEGIN EXCLUSIVE;"

Następnie zsynchronizowałem wszystkie moje transakcje za pomocą zwykłego bloku synchronizacji Java() {}. Wszystko stało się znacznie lepsze. Ale użyłem Cursors do implementacji CursorAdapter dla moich list. Czasami otrzymywałem ten sam "SQLiteException: baza danych jest zablokowana: BEGIN EXCLUSIVE;"

Zakończyłem tworzenie małego wątku bezpiecznego narzędzia sqlite, które obsługuje wszystkie rzeczy bezpieczne dla wątków. Muszę też użyć czegoś takiego jak ArrayAdapter (odczytaj wszystkie dane z kursora i zamknij je po odczytaniu, także zsynchronizuj ten blok) dla mojego interfejsu użytkownika. Tak więc działa OK

Ale nie podoba mi się taki sposób obsługi interfejsu użytkownika, ponieważ interfejs użytkownika stał się naprawdę wolniejszy dzięki temu rozwiązaniu - odczytanie pewnej ilości danych z kursora jest raczej szybkie, ale jest wolniejsze niż użycie CursorAdapter

Kto ma rozwiązanie tego pytania? Dziękujemy

+0

Jak masz na myśli UI dostał wolniej? Czy odpowiedź na dotknięcia itp. Trwa dłużej? Czy po prostu dłużej wyświetlasz dane z bazy danych? Jeśli właśnie czytasz bazę danych, nie używaj transakcji - pozwala to na jednoczesne wykonywanie wielu odczytów z bazy danych. –

+0

@Joseph Earl Miałem na myśli "dłużej wyświetlać dane". i tak, nie używam transakcji podczas odczytu danych. Używam ich tylko wtedy, gdy piszę jakieś dane. –

Odpowiedz

16

W końcu wyszło rozwiązanie. Oto jest.

Przeczytałem niektóre fora, grupy google i okazało się, że baza danych sqlite powinna zostać otwarta tylko raz. Więc zaimplementowałem to używając singletonu.

Zaimplementowałam również kod bazy danych do synchronizowania wszystkich operacji zapisu (aby wiele wątków nie wykonywało operacji zapisu w tym samym czasie). I nie obchodzi mnie otwarcie kursorów, czytanie z nich.

Po kilku dniach testów mam żadnych raportów o błędach z mojego użytkowników, więc myślę, że to działa

W mojej poprzedniej pracy otworzyłem bazy danych SQLite wielokrotnie całej aplikacji, który był problem.

+0

Więc ... jeśli otworzysz bazę danych tylko raz ...jak pozbyć się tego denerwującego ostrzeżenia: 'close() nigdy nie był jawnie wywoływany w bazie danych '/data/data/com.package/databases/name.db' android.database.sqlite.DatabaseObjectNotClosedException: Aplikacja nie zamknęła obiekt kursora lub bazy danych, który został otwarty tutaj " – Cristian

+0

@evgeny przez singiel może czytać jest dozwolone podczas pisania. Użyłem singleton w mojej aplikacji, interfejs użytkownika reaguje bardzo wolno, podczas czytania danych, gdy zapis jest w toku. – Kishore

+0

@Poproś o czytaniu danych w dowolnym momencie. także ... nie czytaj danych w głównym wątku. Zawsze powinieneś czytać dane w tle wątku –

5

SQLite wdraża wyłączny blokada zapisu, model udostępnionej blokady odczytu. Oznacza to, że możesz mieć jednoczesnych czytników aktywnych w bazie danych lub jednego pisarza, nie możesz mieć obu. Jeśli korzystasz z funkcji rejestrowania WAL, możesz mieć jednocześnie jednego pisarza i wiele czytników w bazie danych, ale nadal nie możesz mieć więcej niż jednego programu piszącego. Istnieje doskonała dokumentacja dotycząca współbieżności SQLite here i here.

Możesz rozważyć spojrzenie na Berkeley DB. Berkeley DB oferuje SQL API, który jest całkowicie zgodny z SQLite. Jeśli tak, to dodaliśmy parser, planistę i executor SQLite na wierzchu warstwy magazynowej DB Berkeley. To, co zapewnia programista aplikacji SQLite, to biblioteka zgodna z SQLite, która ma dodatkową skalowalność, współbieżność i niezawodność (HA), a także inne funkcje. Berkeley DB obsługuje wiele czytników i zapisuje jednoczesny dostęp do bazy danych. Istnieją dwa doskonałe dokumenty napisane przez Mike'a Owensa, autora "The Definitive Guide to SQLite", które porównują Berkeley DB i SQLite (Performance comparison, Behavioral Differences).

Nota prawna: Jestem jednym z menedżerów produktu w Berkeley DB, więc jestem trochę stronniczy. Jednak prośby takie jak twoje (potrzeba więcej współbieżności, skalowalności, niezawodności od SQLite), właśnie dlatego zdecydowaliśmy się udostępnić połączoną bibliotekę, która zapewnia najlepsze z obu światów.

+1

Tylko jedna uwaga - pełne łącza (w przeciwieństwie do skróconych) są preferowane na SO, dzięki czemu możemy zobaczyć, dokąd prowadzą. Masz również link do implementacji Androida? Pozdrowienia –

+0

Dzięki za odpowiedź! Ale potrzebuję mojego rozwiązania do pracy na urządzeniach z Androidem. Jak rozumiem, Twoje rozwiązanie nie działa na systemie Android. (jest i wbudowana specjalna wersja sqlite) –

+0

@Joseph, dzięki za sugestię. Zauważyłem, że użyto zarówno krótkich, jak i pełnych linków. Możesz dziś zbudować BDB na Androida i zastąpić bibliotekę SQLite lub połączyć BDB z aplikacją i wywołać ją lokalnie. Tutaj jest dobry post OTN: http://forums.oracle.com/forums/thread.jspa?messageID=9440046� – dsegleau

2

Jeśli używasz tylko jedną klasę singleton pomocnika dostęp do db nie trzeba synchronizować siebie i można użyć pomocnika z wielu czytelników/pisarzy, ponieważ klasa Pomocnik zarządza sam synchronizacji.

Look at this post dla mor szczegółowe wyjaśnienie

+0

Jakiej wersji Androida używasz? Ponieważ były problemy z tym we wcześniejszych wersjach. Nie wiem, kiedy Google to naprawił. Ale wcześniej powinieneś używać własnej synchronizacji. To było dla mnie nieskuteczne, gdy go nie używałem i zostało naprawione zaraz po dodaniu synchronizacji –

0

Użyj Singleton pomocnika do otwierania połączeń.

1) Otwórz dowolną liczbę możliwych do odczytania połączeń, a następnie zamknij je, gdy skończysz.

2) W przypadku połączeń z możliwością zapisu należy otworzyć tylko jedno połączenie do zapisu.

Podczas próby otwarcia innego zapisywalnego połączenia, zwróć już otwarte połączenie. Jeśli nie istnieje połączenie do zapisu, otwórz nowe połączenie, które można zapisać. Zachowaj licznik writable_connection i zamknij zapisywalne połączenie, gdy wszystkie wątki zostaną z nim wykonane.

Zaraz wykonam to i dam znać, jeśli zadziała, kiedy skończę.

0

Cóż, czytając dokumentację moja poprzednia odpowiedź wydaje się niepoprawna. Mam jedną (tonę) SQLiteOpenHelper, więc wygląda na to, że wszystkie połączenia są takie same. Nawet te, które można odczytać, są takie same, jak połączenia do zapisu.

Więc pomijam wywoływanie getReadableDatabase i używam tylko getWritableDatabase. Potem będę przechowywać licznik, aby upewnić się, że baza danych zostanie zamknięta tylko jeden raz.

Ponieważ SQLiteOpenHelper serializuje zapisy, wszystko powinno być w porządku w ten sposób. Po prostu będę musiał śledzić połączenie, aby upewnić się, że nie pozostawiam otwartego na końcu.

Więc w mojej klasie DbHelper Mam teraz:

private int activeDatabaseCount = 0; 
public synchronized SQLiteDatabase openDatabase() { 
    SQLiteDatabase connection = getWritableDatabase(); // always returns the same connection instance 
    activeDatabaseCount++; 
    return connection; 
} 
public synchronized void closeDatabase(SQLiteDatabase connection) { 
    activeDatabaseCount--; 
    if (activeDatabaseCount == 0) { 
     if (connection != null) { 
      if (connection.isOpen()) { 
       connection.close(); 
      } 
     } 
    } 
}