Verwenden des Singleton-Entwurfsmusters für SQLiteDatabase

77

Ich bin ein Neuling auf Android und arbeite an einer einfachen Anwendung, um grundlegende Erfahrungen zu sammeln. Meine App ist ziemlich einfach und besteht unter anderem aus einem Rundfunkempfänger und einigen Aktivitäten. Beide Komponenten verwenden eine einzige Datenbank. Theoretisch kann es also vorkommen, dass beide versuchen, gleichzeitig auf die Datenbank zuzugreifen.

Derzeit instanziiere ich einfach jedes Mal das DB-Objekt (eine SQLite-DB-Hilfsklasse), wenn ich es benötige, und führe die erforderlichen Operationen aus: Abfragen, Einfügen usw.

Nach dem, was ich hier und in einigen anderen Dokumenten gelesen habe, besteht das Problem, dass bei gleichzeitigem Zugriff auf die Datenbank eine Ausnahme "Datenbank gesperrt" angezeigt wird. Ein besserer Ansatz wäre also, eine einzige Instanz dieses Datenbankobjekts zu haben Komponenten verwenden immer dieselbe Datenbankverbindung.

Ist die obige Argumentation richtig? Wäre ein Singleton dann eine ausreichend gute Lösung dafür? Ich weiß, dass einige Puristen vielleicht dagegen argumentieren, aber bitte beachten Sie, dass dies eine ziemlich einfache Anwendung ist, damit ich es mir leisten kann, Dinge zu tun, die ich in anderen Fällen nicht tun würde.

Was wäre sonst eine bessere Option? Ich habe über die Verwendung von Inhaltsanbietern gelesen, aber es wäre zu viel dafür, außerdem bin ich nicht daran interessiert, die Daten mit anderen Aktivitäten zu teilen. Ich habe diesen Beitrag tatsächlich gelesen und fand ihn ziemlich hilfreich.

Dan
quelle

Antworten:

101

Klicken Sie hier, um meinen Blog-Beitrag zu diesem Thema zu sehen.


Hier ist ein Beispielcode, der drei mögliche Ansätze veranschaulicht. Diese ermöglichen den Zugriff auf die Datenbank in der gesamten Anwendung.

Ansatz 1: SQLiteOpenHelper muss ein statisches Datenelement sein

Dies ist nicht die vollständige Implementierung, aber es sollte Ihnen eine gute Vorstellung davon geben, wie Sie die DatabaseHelperKlasse richtig entwerfen . Die statische Factory-Methode stellt sicher, dass jeweils nur eine DatabaseHelper-Instanz vorhanden ist.

/**
 * create custom DatabaseHelper class that extends SQLiteOpenHelper
 */
public class DatabaseHelper extends SQLiteOpenHelper { 
    private static DatabaseHelper mInstance = null;

    private static final String DATABASE_NAME = "databaseName";
    private static final String DATABASE_TABLE = "tableName";
    private static final int DATABASE_VERSION = 1;

    private Context mCxt;

    public static DatabaseHelper getInstance(Context ctx) {
        /** 
         * use the application context as suggested by CommonsWare.
         * this will ensure that you dont accidentally leak an Activitys
         * context (see this article for more information: 
         * http://android-developers.blogspot.nl/2009/01/avoiding-memory-leaks.html)
         */
        if (mInstance == null) {
            mInstance = new DatabaseHelper(ctx.getApplicationContext());
        }
        return mInstance;
    }

    /**
     * constructor should be private to prevent direct instantiation.
     * make call to static factory method "getInstance()" instead.
     */
    private DatabaseHelper(Context ctx) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.mCtx = ctx;
    }
}

Ansatz 2: Abstraktieren Sie die SQLite-Datenbank mit einem "ContentProvider"

Dies ist der Ansatz, den ich vorschlagen würde. Zum einen CursorLoadererfordert die neue Klasse ContentProviders. Wenn Sie also möchten, dass eine Aktivität oder ein Fragment LoaderManager.LoaderCallbacks<Cursor>mit einem implementiert wird CursorLoader(was ich Ihnen vorschlage, es ist magisch!), Müssen Sie ein ContentProviderfür Ihre Anwendung implementieren . Darüber hinaus müssen Sie sich keine Gedanken darüber machen, wie Sie mit ContentProviders einen Singleton-Datenbank-Helfer erstellen können. Rufen Sie einfach getContentResolver()von der Aktivität aus an und das System kümmert sich um alles für Sie (mit anderen Worten, es ist nicht erforderlich, ein Singleton-Muster zu entwerfen, um zu verhindern, dass mehrere Instanzen erstellt werden).

Hoffe das hilft!

Alex Lockwood
quelle
Alex, ich benutze deinen Ansatz (# 2) und ich liebe seine Einfachheit. Aber vor kurzem habe ich ein Problem bemerkt. Ich frage mich, ob Sie mir bei diesem Problem helfen können: stackoverflow.com/questions/10972719/… Übrigens , ich habe gerade bemerkt, dass ich statisches mCxt verwende (vergessen warum). Ich frage mich, ob es etwas mit meinem Problem zu tun hat
Michał K
Der obige Link ist defekt ... hier ist ein aktualisierter: android-developers.blogspot.com/2009/01/…
Alex Lockwood
Hier ist ein Wrapper, den ich geschrieben / verwendet habe, der es etwas einfacher macht, SQLite im Kontext von Android - SqlDb für Android
Kashif
1
Vielen Dank an @AlexLockwood, obwohl der Ansatz Nr. 1 für mich vollkommen sinnvoll ist, dachte ich, man sollte ihn nur verwenden, ContentProviderwenn der Datenbankzugriff mit einer anderen Anwendung geteilt wird.
Ericn
2
Wäre es nicht eine gute Idee, getInstancesynchron zu machen ?
Matthew Mitchell
22

Ich habe nie darüber gelesen, wie man mit einem Singleton auf eine Datenbank auf Android zugreift. Würde es Ihnen etwas ausmachen, einen Link dazu bereitzustellen?

In meinen Apps verwende ich einfache dbhelper-Objekte, keine Singletons. Ich dachte, dies ist eher die Aufgabe der SQL-Engine, um sicherzustellen, dass db nicht gesperrt ist, nicht die Aufgabe Ihrer Android-Klassen, und es funktioniert ziemlich gut für meine größte App, die ist mittelgroß.

Update Nr. 1: Wenn Sie sich die von Ihnen angegebene Referenz ansehen, sieht es so aus, als ob das Problem überhaupt nicht darin besteht, verschiedene Instanzen von a zu verwenden dbhelper. Selbst eine einzelne Instanz kann auf Probleme beim Zugriff auf die Datenbanken stoßen: Das Problem beruht auf gleichzeitigen Zugriffen. Die einzige Möglichkeit, einen ordnungsgemäßen Zugriff verschiedener Threads auf die Datenbank sicherzustellen, besteht in der Verwendung einfacher Thread-Synchronisationsmechanismen ( synchronizedMethoden oder Blöcke), die fast nichts mit der Verwendung eines Singletons zu tun haben.

Update Nr. 2: Der zweite Link, den Sie bereitstellen, zeigt deutlich, dass Singleton-DBhelper-Objekte erforderlich sind, wenn mehrere Threads gleichzeitig in eine Datenbank schreiben. Dies kann beispielsweise passieren, wenn Sie SQL-Vorgänge (Einfügen / Aktualisieren / Löschen) in AsyncTasks ausführen. In diesem Fall würde ein Singleton-Objekt dbhelper einfach alle SQL-Operationen in eine Art Pipeline einfügen und sie der Reihe nach ausführen.

Diese Lösung ist möglicherweise einfacher zu implementieren als die ordnungsgemäße Thread-Synchronisierung mit synchronisierten Methoden in Java. Eigentlich denke ich, dass es irgendwo in Android-Dokumenten mehr Nachdruck auf dieses Problem geben sollte und die Verwendung eines Singleton-DB-Helfers gefördert werden könnte.

Vielen Dank für diese nette Frage und die Folgemaßnahmen.

Snicolas
quelle
Danke Stéphane. Die Artikel, die ich als Basis verwendet habe, sind folgende: stackoverflow.com/questions/2647542/… stackoverflow.com/questions/4302286/… Grüße, Dan
Dan
Das Problem bei dem, was Sie kommentieren, ist, dass (AFAIK) die Verwendung unterschiedlicher Verbindungen zu derselben Datenbank ein Rezept für einen Fehler ist. Also die Verwendung eines Singletons.
Dan
Siehe diesen Thread, der meine Theorie zu unterstützen scheint: stackoverflow.com/questions/2493331/…
Dan