it-swarm.com.de

Was sind die Best Practices für SQLite unter Android?

Was wäre die beste Vorgehensweise beim Ausführen von Abfragen in einer SQLite-Datenbank in einer Android-App?

Ist es sicher, Einfügungen, Löschungen 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 den UI-Thread nicht verwenden sollten, da er die App blockieren kann - was zu einem Anwendung antwortet nicht (ANR) führt.

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

Gibt es Best Practices für diese Szenarien?

671
Vidar Vestnes

Einfügungen, Aktualisierungen, Löschungen und Lesevorgänge von mehreren Threads sind im Allgemeinen in Ordnung, aber Brads Antwort ist nicht korrekt. Sie müssen vorsichtig sein, wie Sie Ihre Verbindungen erstellen und verwenden. Es gibt Situationen, in denen Ihre Aktualisierungsaufrufe fehlschlagen, auch wenn Ihre Datenbank nicht beschädigt wird.

Die grundlegende Antwort.

Das SqliteOpenHelper-Objekt hält an einer Datenbankverbindung fest. Es scheint Ihnen eine Lese- und Schreibverbindung zu bieten, aber das tut es wirklich nicht. Rufen Sie den schreibgeschützten Modus auf, und Sie erhalten die Schreibdatenbankverbindung unabhängig davon.

Also eine Hilfsinstanz, eine Datenbankverbindung. Auch wenn Sie es von mehreren Threads gleichzeitig verwenden. Das SqliteDatabase-Objekt verwendet Java Sperren, um den Zugriff serialisiert zu halten. Wenn also 100 Threads eine Datenbankinstanz haben, werden Aufrufe an die tatsächliche Datenbank auf der Festplatte serialisiert.

Also ein Helfer, eine Datenbankverbindung, die in Java Code serialisiert wird. Ein Thread, 1000 Threads. Wenn Sie eine zwischen ihnen geteilte Hilfsinstanz verwenden, ist der gesamte DB-Zugriffscode seriell. Und das Leben ist gut (ish).

Wenn Sie versuchen, gleichzeitig von verschiedenen Verbindungen aus in die Datenbank zu schreiben, schlägt eine fehl. Es wird nicht warten, bis der erste fertig ist und dann schreiben. Es wird einfach nicht Ihre Änderung schreiben. Schlimmer noch, wenn Sie in der SQLite-Datenbank nicht die richtige Version von insert/update aufrufen, wird keine Ausnahme gemeldet. Sie erhalten nur eine Nachricht in Ihrem LogCat, und das wird es sein.

Also mehrere Threads? Verwenden Sie einen Helfer. Zeitraum. Wenn Sie wissen, dass nur ein Thread schreibt, können Sie möglicherweise mehrere Verbindungen verwenden und Ihre Lesevorgänge sind schneller, aber der Käufer ist vorsichtig. Ich habe nicht so viel getestet.

Hier ist ein Blog-Beitrag mit viel mehr Details und einer Beispiel-App.

Gray und ich arbeiten gerade an einem ORM-Tool, das auf seinem Ormlite basiert und von Haus aus mit Datenbankimplementierungen von Android zusammenarbeitet und der sicheren Erstellungs-/Aufrufstruktur folgt, die ich im Blogbeitrag beschreibe. Das sollte sehr bald raus sein. Schau mal.


In der Zwischenzeit gibt es einen folgenden Blog-Beitrag:

Überprüfen Sie auch die Gabel mit 2point0 des zuvor erwähnten Verriegelungsbeispiels:

620
Kevin Galligan

Gleichzeitiger Datenbankzugriff

Gleicher Artikel in meinem Blog (ich formatiere gerne mehr)

Ich habe einen kleinen Artikel geschrieben, in dem beschrieben wird, wie der Zugriff auf den Datenbank-Thread Android sicher gemacht wird.


Angenommen, Sie haben Ihren eigenen SQLiteOpenHelper .

public class DatabaseHelper extends SQLiteOpenHelper { ... }

Jetzt möchten Sie Daten in separaten Threads in die Datenbank schreiben.

 // Thread 1
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

 // Thread 2
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

Sie erhalten folgende Meldung in Ihrem Logcat und eine Ihrer Änderungen wird nicht geschrieben.

Android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

Dies geschieht, weil jedes Mal, wenn Sie ein neues SQLiteOpenHelper -Objekt erstellen, tatsächlich eine neue Datenbankverbindung hergestellt wird. Wenn Sie versuchen, gleichzeitig von verschiedenen Verbindungen aus in die Datenbank zu schreiben, schlägt eine fehl. (von der Antwort oben)

Um eine Datenbank mit mehreren Threads zu verwenden, müssen Sie sicherstellen, dass Sie eine Datenbankverbindung verwenden.

Machen wir eine Singleton-Klasse Database Manager, die ein einzelnes SQLiteOpenHelper -Objekt enthält und zurückgibt.

public class DatabaseManager {

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initialize(..) method first.");
        }

        return instance;
    }

    public SQLiteDatabase getDatabase() {
        return new mDatabaseHelper.getWritableDatabase();
    }

}

Der aktualisierte Code, mit dem Daten in separate Threads in die Datenbank geschrieben werden, sieht folgendermaßen aus.

 // In your application class
 DatabaseManager.initializeInstance(new MySQLiteOpenHelper());
 // Thread 1
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();

 // Thread 2
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();

Dies wird Ihnen einen weiteren Absturz bringen.

Java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase

Da wir nur eine Datenbankverbindung verwenden, gibt die Methode getDatabase () dieselbe Instanz von SQLiteDatabase Objekt für Thread1 und Thread2 zurück =. Was passiert, Thread1 kann die Datenbank schließen, während Thread2 sie noch verwendet. Aus diesem Grund ist IllegalStateException abgestürzt.

Wir müssen sicherstellen, dass niemand die Datenbank verwendet und sie erst dann schließen. Einige Leute auf stackoveflow haben empfohlen, Ihre SQLiteDatabase niemals zu schließen. Dies führt zu der folgenden Logcat-Meldung.

Leak found
Caused by: Java.lang.IllegalStateException: SQLiteDatabase created and never closed

Arbeitsprobe

public class DatabaseManager {

    private int mOpenCounter;

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;
    private SQLiteDatabase mDatabase;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initializeInstance(..) method first.");
        }

        return instance;
    }

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

    public synchronized void closeDatabase() {
        mOpenCounter--;
        if(mOpenCounter == 0) {
            // Closing database
            mDatabase.close();

        }
    }

}

Verwenden Sie es wie folgt.

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way

Jedes Mal, wenn Sie eine Datenbank benötigen, sollten Sie die openDatabase () -Methode der DatabaseManager -Klasse aufrufen. Innerhalb dieser Methode gibt es einen Zähler, der angibt, wie oft die Datenbank geöffnet wurde. Wenn es gleich eins ist, müssen wir eine neue Datenbankverbindung erstellen. Wenn nicht, ist die Datenbankverbindung bereits erstellt.

Das gleiche passiert in der closeDatabase () Methode. Jedes Mal, wenn wir diese Methode aufrufen, wird der Leistungsindikator verringert, und wenn er auf Null geht, wird die Datenbankverbindung geschlossen.


Jetzt sollten Sie in der Lage sein, Ihre Datenbank zu verwenden und sicherzustellen, dass sie threadsicher ist.

186
Dmytro Danylyk
  • Verwenden Sie Thread oder AsyncTask für lange Betriebszeiten (50 ms +). Testen Sie Ihre App, um zu sehen, wo das ist. Die meisten Operationen erfordern (wahrscheinlich) keinen Thread, da die meisten Operationen (wahrscheinlich) nur wenige Zeilen umfassen. Verwenden Sie einen Thread für Massenoperationen.
  • Geben Sie eine SQLiteDatabase Instanz für jede DB auf der Festplatte zwischen Threads frei und implementieren Sie ein Zählsystem, um offene Verbindungen zu verfolgen.

Gibt es Best Practices für diese Szenarien?

Teilen Sie ein statisches Feld zwischen all Ihren Klassen. Ich hatte immer einen Singleton für das und andere Dinge, die geteilt werden mussten. Ein Zählschema (im Allgemeinen mit AtomicInteger) sollte auch verwendet werden, um sicherzustellen, dass Sie die Datenbank niemals vorzeitig schließen oder offen lassen.

Meine Lösung:

Die aktuellste Version finden Sie unter https://github.com/JakarCo/databasemanager , aber ich werde versuchen, den Code auch hier auf dem neuesten Stand zu halten. Wenn Sie meine Lösung verstehen möchten, schauen Sie sich den Code an und lesen Sie meine Notizen. Meine Notizen sind normalerweise ziemlich hilfreich.

  1. kopieren Sie den Code in eine neue Datei mit dem Namen DatabaseManager. (oder lade es von github herunter)
  2. erweitern Sie DatabaseManager und implementieren Sie onCreate und onUpgrade wie gewohnt. Sie können mehrere Unterklassen der einen Klasse DatabaseManager erstellen, um unterschiedliche Datenbanken auf dem Datenträger zu haben.
  3. Instanziieren Sie Ihre Unterklasse und rufen Sie getDb() auf, um die Klasse SQLiteDatabase zu verwenden.
  4. Rufen Sie close() für jede von Ihnen instanziierte Unterklasse auf

Der Code zum Kopieren/Einfügen :

import Android.content.Context;
import Android.database.sqlite.SQLiteDatabase;

import Java.util.concurrent.ConcurrentHashMap;

/** Extend this class and use it as an SQLiteOpenHelper class
 *
 * DO NOT distribute, sell, or present this code as your own. 
 * for any distributing/selling, or whatever, see the info at the link below
 *
 * Distribution, attribution, legal stuff,
 * See https://github.com/JakarCo/databasemanager
 * 
 * If you ever need help with this code, contact me at [email protected] (or [email protected] )
 * 
 * Do not sell this. but use it as much as you want. There are no implied or express warranties with this code. 
 *
 * This is a simple database manager class which makes threading/synchronization super easy.
 *
 * Extend this class and use it like an SQLiteOpenHelper, but use it as follows:
 *  Instantiate this class once in each thread that uses the database. 
 *  Make sure to call {@link #close()} on every opened instance of this class
 *  If it is closed, then call {@link #open()} before using again.
 * 
 * Call {@link #getDb()} to get an instance of the underlying SQLiteDatabse class (which is synchronized)
 *
 * I also implement this system (well, it's very similar) in my <a href="http://androidslitelibrary.com">Android SQLite Libray</a> at http://androidslitelibrary.com
 * 
 *
 */
abstract public class DatabaseManager {

    /**See SQLiteOpenHelper documentation
    */
    abstract public void onCreate(SQLiteDatabase db);
    /**See SQLiteOpenHelper documentation
     */
    abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
    /**Optional.
     * *
     */
    public void onOpen(SQLiteDatabase db){}
    /**Optional.
     * 
     */
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
    /**Optional
     * 
     */
    public void onConfigure(SQLiteDatabase db){}



    /** The SQLiteOpenHelper class is not actually used by your application.
     *
     */
    static private class DBSQLiteOpenHelper extends SQLiteOpenHelper {

        DatabaseManager databaseManager;
        private AtomicInteger counter = new AtomicInteger(0);

        public DBSQLiteOpenHelper(Context context, String name, int version, DatabaseManager databaseManager) {
            super(context, name, null, version);
            this.databaseManager = databaseManager;
        }

        public void addConnection(){
            counter.incrementAndGet();
        }
        public void removeConnection(){
            counter.decrementAndGet();
        }
        public int getCounter() {
            return counter.get();
        }
        @Override
        public void onCreate(SQLiteDatabase db) {
            databaseManager.onCreate(db);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            databaseManager.onUpgrade(db, oldVersion, newVersion);
        }

        @Override
        public void onOpen(SQLiteDatabase db) {
            databaseManager.onOpen(db);
        }

        @Override
        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            databaseManager.onDowngrade(db, oldVersion, newVersion);
        }

        @Override
        public void onConfigure(SQLiteDatabase db) {
            databaseManager.onConfigure(db);
        }
    }

    private static final ConcurrentHashMap<String,DBSQLiteOpenHelper> dbMap = new ConcurrentHashMap<String, DBSQLiteOpenHelper>();

    private static final Object lockObject = new Object();


    private DBSQLiteOpenHelper sqLiteOpenHelper;
    private SQLiteDatabase db;
    private Context context;

    /** Instantiate a new DB Helper. 
     * <br> SQLiteOpenHelpers are statically cached so they (and their internally cached SQLiteDatabases) will be reused for concurrency
     *
     * @param context Any {@link Android.content.Context} belonging to your package.
     * @param name The database name. This may be anything you like. Adding a file extension is not required and any file extension you would like to use is fine.
     * @param version the database version.
     */
    public DatabaseManager(Context context, String name, int version) {
        String dbPath = context.getApplicationContext().getDatabasePath(name).getAbsolutePath();
        synchronized (lockObject) {
            sqLiteOpenHelper = dbMap.get(dbPath);
            if (sqLiteOpenHelper==null) {
                sqLiteOpenHelper = new DBSQLiteOpenHelper(context, name, version, this);
                dbMap.put(dbPath,sqLiteOpenHelper);
            }
            //SQLiteOpenHelper class caches the SQLiteDatabase, so this will be the same SQLiteDatabase object every time
            db = sqLiteOpenHelper.getWritableDatabase();
        }
        this.context = context.getApplicationContext();
    }
    /**Get the writable SQLiteDatabase
     */
    public SQLiteDatabase getDb(){
        return db;
    }

    /** Check if the underlying SQLiteDatabase is open
     *
     * @return whether the DB is open or not
     */
    public boolean isOpen(){
        return (db!=null&&db.isOpen());
    }


    /** Lowers the DB counter by 1 for any {@link DatabaseManager}s referencing the same DB on disk
     *  <br />If the new counter is 0, then the database will be closed.
     *  <br /><br />This needs to be called before application exit.
     * <br />If the counter is 0, then the underlying SQLiteDatabase is <b>null</b> until another DatabaseManager is instantiated or you call {@link #open()}
     *
     * @return true if the underlying {@link Android.database.sqlite.SQLiteDatabase} is closed (counter is 0), and false otherwise (counter > 0)
     */
    public boolean close(){
        sqLiteOpenHelper.removeConnection();
        if (sqLiteOpenHelper.getCounter()==0){
            synchronized (lockObject){
                if (db.inTransaction())db.endTransaction();
                if (db.isOpen())db.close();
                db = null;
            }
            return true;
        }
        return false;
    }
    /** Increments the internal db counter by one and opens the db if needed
    *
    */
    public void open(){
        sqLiteOpenHelper.addConnection();
        if (db==null||!db.isOpen()){
                synchronized (lockObject){
                    db = sqLiteOpenHelper.getWritableDatabase();
                }
        } 
    }
}
17
Reed

Die Datenbank ist mit Multithreading sehr flexibel. Meine Apps haben ihre DBs aus vielen verschiedenen Threads gleichzeitig abgerufen und es funktioniert einwandfrei. In einigen Fällen habe ich mehrere Prozesse gleichzeitig auf die DB und das funktioniert auch.

Ihre asynchronen Aufgaben - verwenden Sie die gleiche Verbindung, wenn Sie können, aber wenn Sie müssen, ist es OK, von verschiedenen Aufgaben aus auf die Datenbank zuzugreifen.

11
Brad Hein

Die Antwort von Dmytro funktioniert gut für meinen Fall. Ich denke, es ist besser, die Funktion als synchronisiert zu deklarieren. Zumindest für meinen Fall würde es andernfalls eine Nullzeigerausnahme auslösen, z. getWritableDatabase wurde in einem Thread noch nicht zurückgegeben und openDatabse wurde in der Zwischenzeit in einem anderen Thread aufgerufen.

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

nachdem ich ein paar Stunden damit zu kämpfen hatte, stellte ich fest, dass Sie nur ein DB-Hilfsobjekt pro DB-Ausführung verwenden können. Zum Beispiel,

for(int x = 0; x < someMaxValue; x++)
{
    db = new DBAdapter(this);
    try
    {

        db.addRow
        (
                NamesStringArray[i].toString(), 
                StartTimeStringArray[i].toString(),
                EndTimeStringArray[i].toString()
        );

    }
    catch (Exception e)
    {
        Log.e("Add Error", e.toString());
        e.printStackTrace();
    }
    db.close();
}

wie vorgeschlagen an:

db = new DBAdapter(this);
for(int x = 0; x < someMaxValue; x++)
{

    try
    {
        // ask the database manager to add a row given the two strings
        db.addRow
        (
                NamesStringArray[i].toString(), 
                StartTimeStringArray[i].toString(),
                EndTimeStringArray[i].toString()
        );

    }
    catch (Exception e)
    {
        Log.e("Add Error", e.toString());
        e.printStackTrace();
    }

}
db.close();

das Erstellen eines neuen DBAdapters bei jedem Durchlaufen der Schleife war die einzige Möglichkeit, meine Zeichenfolgen über meine Hilfsklasse in eine Datenbank zu bekommen.

5
dell116

Mein Verständnis von SQLiteDatabase-APIs ist, dass Sie es sich nicht leisten können, wenn Sie eine Anwendung mit mehreren Threads haben, mehr als 1 SQLiteDatabase-Objekt auf eine einzelne Datenbank zu verweisen.

Das Objekt kann definitiv erstellt werden, aber die Einfügungen/Aktualisierungen schlagen fehl, wenn (auch) verschiedene Threads/Prozesse unterschiedliche SQLiteDatabase-Objekte verwenden (wie wir es in JDBC Connection verwenden).

Die einzige Lösung besteht darin, bei 1 SQLiteDatabase-Objekten zu bleiben. Wenn eine startTransaction () in mehr als 1 Thread verwendet wird, verwaltet Android das Sperren für verschiedene Threads und lässt zu, dass jeweils nur 1 Thread exklusiv aktualisiert wird Zugriff.

Sie können auch "Reads" aus der Datenbank ausführen und dasselbe SQLiteDatabase-Objekt in einem anderen Thread verwenden (während ein anderer Thread schreibt), und es würde niemals zu Datenbankbeschädigungen kommen, dh "read thread" würde die Daten nicht aus der Datenbank lesen, bis " "Thread schreiben" schreibt die Daten fest, obwohl beide dasselbe SQLiteDatabase-Objekt verwenden.

Dies unterscheidet sich von dem Verbindungsobjekt in JDBC. Wenn Sie das Verbindungsobjekt zwischen Lese- und Schreibthreads umgehen (verwenden), werden wahrscheinlich auch nicht festgeschriebene Daten gedruckt.

In meiner Unternehmensanwendung versuche ich, bedingte Prüfungen zu verwenden, damit der UI-Thread niemals warten muss, während der BG-Thread das SQLiteDatabase-Objekt (ausschließlich) enthält. Ich versuche, UI-Aktionen vorherzusagen, und schiebe den BG-Thread für 'x' Sekunden vom Laufen. Außerdem kann PriorityQueue verwaltet werden, um das Verteilen von SQLiteDatabase-Verbindungsobjekten so zu verwalten, dass der UI-Thread es zuerst erhält.

4
Swaroop

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

Es enthält auch eine neue ORM-Bibliothek mit dem Namen Room

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

User.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();
}
4
Zimbo Rodger

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

Ich hatte eine Datenbank-Wrapper-Klasse geschrieben, die eine close() enthielt, die den Helper close als Spiegel von open() aufrief, die getWriteableDatabase aufrief und dann zu einer ContentProvider migrierte. Das Modell für ContentProvider verwendet SQLiteDatabase.close() nicht, was meiner Meinung nach ein großer Hinweis ist, da der Code getWriteableDatabase verwendet Ich bin auf ein getWriteableDatabase/rawQuery-Modell migriert.

Ich benutze einen Singleton und es gibt den etwas ominösen Kommentar in der Abschlussdokumentation

Close any Datenbankobjekt öffnen

(meine Kühnheit).

Daher hatte ich zeitweise Abstürze, bei denen ich Hintergrundthreads für den Zugriff auf die Datenbank verwende und sie gleichzeitig mit dem Vordergrund ausgeführt werden.

Ich denke also, dass close() das Schließen der Datenbank erzwingt, unabhängig von allen anderen Threads, die Verweise enthalten. close() macht also nicht einfach den entsprechenden getWriteableDatabase rückgängig, sondern erzwingt das Schließen beliebige offene Anfragen. Meistens ist dies kein Problem, da es sich bei dem Code um Single-Threading handelt. In Fällen mit mehreren Threads besteht jedoch immer die Möglichkeit, dass die Synchronisierung unterbrochen wird.

Wenn Sie Kommentare an einer anderen Stelle gelesen haben, die erklären, dass die SqLiteDatabaseHelper-Codeinstanz zählt, möchten Sie nur dann schließen, wenn Sie eine Sicherungskopie erstellen und alle Verbindungen schließen und SqLite erzwingen möchten Schreiben Sie alle zwischengespeicherten Daten, die möglicherweise herumlungern, weg. Mit anderen Worten, beenden Sie alle Aktivitäten in der Anwendungsdatenbank, schließen Sie sie, falls der Helper den Überblick verloren hat, führen Sie alle Aktivitäten auf Dateiebene (Sichern/Wiederherstellen) aus und beginnen Sie dann von vorne.

Obwohl es sich nach einer guten Idee anhört, das Schließen auf kontrollierte Weise zu versuchen, ist die Realität, dass Android sich das Recht vorbehält, Ihr VM in den Papierkorb zu werfen, sodass jedes Schließen das Risiko von zwischengespeicherten Updates verringert Wird nicht geschrieben, kann jedoch nicht garantiert werden, dass das Gerät überlastet ist. Wenn Sie Ihre Cursor und Verweise auf Datenbanken (die keine statischen Mitglieder sein sollten) ordnungsgemäß freigegeben haben, hat der Helfer die Datenbank trotzdem geschlossen.

Meiner Meinung nach lautet der Ansatz also:

Verwenden Sie getWriteableDatabase, um von einem Singleton-Wrapper aus zu öffnen. (Ich habe eine abgeleitete Anwendungsklasse verwendet, um den Anwendungskontext aus einer statischen bereitzustellen, um die Notwendigkeit eines Kontexts zu beheben.).

Niemals direkt in der Nähe anrufen.

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

Wenn Sie die Verarbeitung auf Dateiebene ausführen, halten Sie alle Datenbankaktivitäten an und rufen Sie close auf, falls ein außer Kontrolle geratener Thread vorliegt. Dabei wird davon ausgegangen, dass Sie ordnungsgemäße Transaktionen schreiben, sodass der außer Kontrolle geratene Thread fehlschlägt und die geschlossene Datenbank zumindest ordnungsgemäße Transaktionen aufweist als möglicherweise eine Kopie auf Dateiebene einer Teiltransaktion.

3
Ian Spencer

Ich weiß, dass die Antwort zu spät ist, aber der beste Weg, um SQLite-Abfragen in Android auszuführen, ist ein benutzerdefinierter Inhaltsanbieter. Auf diese Weise wird die Benutzeroberfläche von der Datenbankklasse (der Klasse, die die SQLiteOpenHelper-Klasse erweitert) entkoppelt. Auch die Abfragen werden in einem Hintergrundthread (Cursor Loader) ausgeführt.

0
Theo