727 Stimmen

Wie kann ich Gleichzeitigkeitsprobleme bei der Verwendung von SQLite unter Android vermeiden?

Was sind die besten Praktiken bei der Ausführung von Abfragen auf einer SQLite-Datenbank in einer Android-App?

Ist es sicher, Einfüge-, Lösch- und Auswahlabfragen aus dem doInBackground einer AsyncTask auszuführen? Oder sollte ich den UI-Thread verwenden? Ich nehme an, dass Datenbankabfragen "schwer" sein können und nicht den UI-Thread verwenden sollten, da dies die Anwendung blockieren kann - was zu einem Anwendung antwortet nicht (ANR).

Wenn ich mehrere AsyncTasks habe, sollten sie sich eine Verbindung teilen oder sollten sie jeweils eine Verbindung öffnen?

Gibt es bewährte Verfahren für diese Szenarien?

8voto

gonglong Punkte 562

Die Antwort von Dmytro funktioniert in meinem Fall gut. Ich denke, es ist besser, die Funktion als synchronisiert zu deklarieren. zumindest für meinen Fall, würde es sonst Null Zeiger Ausnahme aufrufen, z.B. getWritableDatabase noch nicht in einem Thread zurückgegeben und openDatabse in einem anderen Thread inzwischen aufgerufen.

public synchronized SQLiteDatabase openDatabase() {
    if(mOpenCounter.incrementAndGet() == 1) {
        // Opening new database
        mDatabase = mDatabaseHelper.getWritableDatabase();
    }
    return mDatabase;
}

6voto

David Punkte 1789

Sie können versuchen, einen neuen Architekturansatz anzuwenden angekündigt auf der Google I/O 2017.

Es enthält auch eine neue ORM-Bibliothek namens Zimmer

Es enthält drei Hauptkomponenten: @Entity, @Dao und @Datenbank

Benutzer.java

@Entity
public class User {
  @PrimaryKey
  private int uid;

  @ColumnInfo(name = "first_name")
  private String firstName;

  @ColumnInfo(name = "last_name")
  private String lastName;

  // Getters and setters are ignored for brevity,
  // but they're required for Room to work.
}

UserDao.java

@Dao
public interface UserDao {
  @Query("SELECT * FROM user")
  List<User> getAll();

  @Query("SELECT * FROM user WHERE uid IN (:userIds)")
  List<User> loadAllByIds(int[] userIds);

  @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
       + "last_name LIKE :last LIMIT 1")
  User findByName(String first, String last);

  @Insert
  void insertAll(User... users);

  @Delete
  void delete(User user);
}

AppDatabase.java

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
  public abstract UserDao userDao();
}

5voto

Swaroop Punkte 898

Mein Verständnis von SQLiteDatabase APIs ist, dass im Falle Sie eine Multi-Thread-Anwendung haben, können Sie es sich nicht leisten, mehr als ein SQLiteDatabase-Objekt, das auf eine einzelne Datenbank zeigt.

Das Objekt kann zwar erstellt werden, aber die Einfügungen/Aktualisierungen schlagen fehl, wenn verschiedene Threads/Prozesse (auch) verschiedene SQLiteDatabase-Objekte verwenden (wie bei der JDBC-Verbindung).

Die einzige Lösung besteht darin, mit einem SQLiteDatabase-Objekt zu arbeiten und immer dann, wenn eine startTransaction() in mehr als einem Thread verwendet wird, verwaltet Android das Locking über verschiedene Threads hinweg und erlaubt nur jeweils einem Thread den exklusiven Aktualisierungszugriff.

Sie können auch "Reads" aus der Datenbank durchführen und dasselbe SQLiteDatabase-Objekt in einem anderen Thread verwenden (während ein anderer Thread schreibt), und es würde nie zu einer Datenbankbeschädigung kommen, d.h. der "Read-Thread" würde die Daten nicht aus der Datenbank lesen, bis der "Write-Thread" die Daten festschreibt, obwohl beide dasselbe SQLiteDatabase-Objekt verwenden.

Dies unterscheidet sich von der Art und Weise, wie das Verbindungsobjekt in JDBC ist, wenn Sie das Verbindungsobjekt zwischen Lese- und Schreib-Threads weitergeben (dasselbe verwenden), dann würden wir wahrscheinlich auch unbestätigte Daten drucken.

In meiner Unternehmensanwendung versuche ich, bedingte Prüfungen zu verwenden, damit der UI-Thread nie warten muss, während der BG-Thread das SQLiteDatabase-Objekt (ausschließlich) hält. Ich versuche, UI-Aktionen vorherzusagen und die Ausführung des BG-Threads um 'x' Sekunden zu verschieben. Man kann auch eine PriorityQueue einrichten, um die Vergabe von SQLiteDatabase Connection-Objekten so zu verwalten, dass der UI-Thread sie zuerst erhält.

3voto

Ian Spencer Punkte 526

Nachdem ich einige Probleme hatte, denke ich, dass ich verstanden habe, warum ich etwas falsch gemacht habe.

Ich hatte eine Datenbank-Wrapper-Klasse geschrieben, die eine close() die den Helfer als Spiegel von open() die getWriteableDatabase aufriefen, und sind dann zu einer ContentProvider . Das Modell für ContentProvider verwendet nicht SQLiteDatabase.close() was meiner Meinung nach ein wichtiger Hinweis ist, denn der Code verwendet getWriteableDatabase In einigen Fällen hatte ich immer noch direkten Zugriff (Abfragen zur Bildschirmvalidierung), so dass ich auf ein getWriteableDatabase/rawQuery-Modell umgestiegen bin.

Ich verwende ein Singleton und es gibt den etwas ominösen Kommentar in der Close-Dokumentation

Schließen Sie 何れも Datenbankobjekt öffnen

(Fettdruck von mir).

Ich hatte also zeitweise Abstürze, bei denen ich Hintergrund-Threads für den Zugriff auf die Datenbank benutzte, die zur gleichen Zeit wie die Vordergrund-Threads liefen.

Ich denke also close() erzwingt das Schließen der Datenbank unabhängig von allen anderen Threads, die Referenzen halten - also close() selbst ist nicht einfach die Rückgängigmachung des Abgleichs getWriteableDatabase aber zwangsweise schließen 何れも offene Anfragen. In den meisten Fällen ist dies kein Problem, da der Code einfädig ist, aber in Fällen mit mehreren Fäden besteht immer die Möglichkeit, dass das Öffnen und Schließen nicht synchron ist.

Nachdem ich an anderer Stelle Kommentare gelesen habe, die erklären, dass die SqLiteDatabaseHelper-Code-Instanz zählt, ist der einzige Zeitpunkt, an dem Sie eine Schließung wünschen, die Situation, in der Sie eine Sicherungskopie erstellen möchten, und Sie möchten erzwingen, dass alle Verbindungen geschlossen werden und SqLite dazu zwingen, alle zwischengespeicherten Daten, die möglicherweise herumliegen, wegzuschreiben.

Obwohl es sich wie eine gute Idee anhört, zu versuchen, die VM kontrolliert zu schließen, ist es in der Realität so, dass Android sich das Recht vorbehält, die VM zu löschen, so dass jedes Schließen das Risiko reduziert, dass zwischengespeicherte Updates nicht geschrieben werden, aber es kann nicht garantiert werden, wenn das Gerät gestresst ist, und wenn Sie Ihre Cursor und Verweise auf Datenbanken (die keine statischen Mitglieder sein sollten) korrekt freigegeben haben, dann wird der Helfer die Datenbank sowieso geschlossen haben.

Meiner Meinung nach ist der Ansatz also folgender:

Verwenden Sie getWriteableDatabase zum Öffnen aus einer Singleton-Verschalung (ich habe eine abgeleitete Anwendungsklasse verwendet, um den Anwendungskontext aus einer statischen Klasse bereitzustellen, damit kein Kontext erforderlich ist).

Rufen Sie niemals direkt in der Nähe an.

Speichern Sie die resultierende Datenbank niemals in einem Objekt, das keinen offensichtlichen Anwendungsbereich hat, und verlassen Sie sich auf die Referenzzählung, um ein implizites close() auszulösen.

Wenn Sie auf Dateiebene arbeiten, bringen Sie alle Datenbankaktivitäten zum Stillstand und rufen Sie dann "close" auf, für den Fall, dass ein "Runaway Thread" auftritt.

0voto

Theo Punkte 2989

Ich weiß, dass die Antwort ist spät, aber der beste Weg, um Sqlite-Abfragen in Android ausführen ist durch eine benutzerdefinierte Content-Provider. Auf diese Weise ist die Benutzeroberfläche von der Datenbankklasse entkoppelt (die Klasse, die die SQLiteOpenHelper-Klasse erweitert). Außerdem werden die Abfragen in einem Hintergrund-Thread (Cursor Loader) ausgeführt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X