Jakie są najlepsze praktyki dla SQLite na Androida?

Jakie byłyby najlepsze praktyki podczas wykonywania zapytań w bazie danych SQLite w aplikacji na Androida?

Czy bezpieczne jest uruchamianie wstawiania, usuwania i wybierania zapytań z Doinbackground funkcji AsyncTask? Czy powinienem użyć wątku UI? Przypuszczam, że zapytania do bazy danych mogą być "ciężkie" i nie powinny używać wątku interfejsu użytkownika, ponieważ może on zablokować aplikację - w rezultacie aplikacja nie odpowiada (ANR).

Jeśli mam kilka asynchronicznych zadań, czy powinny one współdzielić połączenie lub czy każdy z nich powinien otworzyć połączenie?

Czy są jakieś najlepsze praktyki dla tych scenariuszy?

Author: Peter Mortensen, 2010-03-22

10 answers

Wstawianie, aktualizacje, usuwanie i odczyty są ogólnie OK z wielu wątków, ale odpowiedź nie jest poprawna. Musisz być ostrożny z tym, jak tworzysz swoje połączenia i z nich korzystasz. Istnieją sytuacje, w których wywołania aktualizacji nie powiodą się, nawet jeśli baza danych nie zostanie uszkodzona.

Podstawowa odpowiedź.

Obiekt sqliteopenhelper utrzymuje jedno połączenie z bazą danych. Wydaje się, że oferuje połączenie odczytu i zapisu, ale tak naprawdę nie. Wywołaj tylko do odczytu, a otrzymasz połączenie zapisu bazy danych niezależnie od tego.

Więc jedna instancja helpera, jedno połączenie db. Nawet jeśli używasz go z wielu wątków, jedno połączenie na raz. Obiekt sqlitedatabase używa blokad java do utrzymywania dostępu serializowanego. Tak więc, jeśli 100 wątków ma jedną instancję db, wywołania do rzeczywistej bazy danych na dysku są serializowane.

Więc, jeden helper, jedno połączenie db, które jest serializowane w kodzie java. Jeden wątek, 1000 wątków, jeśli używasz jednego pomocnika instancja współdzielona między nimi, cały Twój kod dostępu db jest szeregowy. A życie jest dobre.

Jeśli spróbujesz zapisać do bazy danych z rzeczywistych różnych połączeń w tym samym czasie, jeden z nich się nie powiedzie. Nie będzie czekać do pierwszego jest zrobione, a następnie napisać. Po prostu nie napisze Twojej zmiany. Co gorsza, jeśli nie wywołasz odpowiedniej wersji insert/update w bazie SQLiteDatabase, nie otrzymasz wyjątku. Dostaniesz wiadomość w LogCat i to będzie to.

Więc, wiele wątków? Użyj jednego pomocnika. Kropka. Jeśli wiesz, że tylko jeden wątek będzie pisać, możesz być w stanie korzystać z wielu połączeń, a odczyty będą szybsze, ale KUPUJĄCY UWAŻAJ. Nie testowałem za dużo.

Oto wpis na blogu o wiele bardziej szczegółowo i przykładowa aplikacja.

Gray i ja jesteśmy owijanie narzędzia ORM, oparte na jego Ormlite, które działa natywnie z implementacjami baz danych Androida i podąża za bezpieczną strukturą tworzenia / wywoływania, którą opisuję w poście na blogu. To powinno się wkrótce skończyć. Spójrz.


W międzyczasie pojawia się post na blogu:

Sprawdź również widelec przez 2point0 Z wcześniej wspomnianego ryglowania przykład:

  • Na Githubie nie ma żadnych ograniczeń co do tego, czy jest to system operacyjny, czy system operacyjny.]}
 601
Author: Kevin Galligan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-17 06:39:23

Współbieżny Dostęp Do Bazy Danych

Ten sam artykuł na moim blogu (bardziej lubię formatowanie)

napisałem mały artykuł, który opisuje, jak uczynić dostęp do wątku bazy danych Androida bezpiecznym.


Zakładając, że masz swój własny SQLiteOpenHelper .

public class DatabaseHelper extends SQLiteOpenHelper { ... }

Teraz chcesz zapisywać dane do bazy danych w oddzielnych wątkach.

 // 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();

Otrzymasz następujący komunikat w logcat i jedna z Twoich zmian nie będzie napisane.

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

Dzieje się tak, ponieważ za każdym razem, gdy tworzysz nowy obiekt sqliteopenhelper, tworzysz nowe połączenie z bazą danych. Jeśli spróbujesz zapisać do bazy danych z rzeczywistych różnych połączeń w tym samym czasie, jeden z nich się nie powiedzie. (z odpowiedzi powyżej)

Aby używać bazy danych z wieloma wątkami musimy upewnić się, że używamy jednego połączenia z bazą danych.

Zróbmy klasę singleton menedżer bazy danych, która będzie trzymać i zwracać pojedynczy obiekt SQLiteOpenHelper.

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

}

Zaktualizowany kod zapisujący dane do bazy danych w oddzielnych wątkach będzie wyglądał tak.

 // 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();
To przyniesie ci kolejny wypadek.
java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase

Ponieważ używamy tylko jednego połączenia z bazą danych, metoda getDatabase () zwraca tę samą instancję sqlitedatabase obiektu dla Thread1 i Thread2 . Co się dzieje, Thread1 może zamknąć bazę danych, podczas gdy Thread2 jest nadal go używam. Dlatego mamy IllegalStateException crash.

Musimy się upewnić, że nikt nie używa bazy danych i dopiero wtedy ją zamykamy. Niektórzy użytkownicy stoskoveflow zalecali, aby nigdy nie zamykać SQLiteDatabase. To nie tylko brzmi głupio, ale także honoruje Cię następującym komunikatem logcat.

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

Próbka robocza

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();

        }
    }

}

Użyj go w następujący sposób.

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

Za każdym razem, gdy potrzebujesz bazy danych, powinieneś wywołać openDatabase () metodę databasemanager class. Wewnątrz tej metody mamy licznik, który wskazuje ile razy baza danych jest otwierana. Jeżeli jest równa 1, oznacza to, że musimy utworzyć nowe połączenie do bazy danych, jeśli nie, połączenie do bazy danych jest już utworzone.

To samo dzieje się w metodzie closeDatabase () . Za każdym razem, gdy wywołamy tę metodę, licznik jest zmniejszany, gdy dojdzie do zera, zamykamy połączenie z bazą danych.


Teraz powinieneś być w stanie korzystać z bazy danych i upewnij się, że jest bezpieczny.

 177
Author: Dmytro Danylyk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-05 09:22:24
  • użyj Thread lub AsyncTask do długotrwałych operacji (50ms+). Przetestuj swoją aplikację, aby zobaczyć, gdzie to jest. Większość operacji (prawdopodobnie) nie wymaga wątku, ponieważ większość operacji (prawdopodobnie) obejmuje tylko kilka wierszy. Użyj wątku do operacji masowych.
  • Udostępnij jedną instancję SQLiteDatabase dla każdego DB na dysku pomiędzy wątkami i zaimplementuj system liczenia, aby śledzić otwarte połączenia.

Czy są jakieś najlepsze praktyki dla tych scenariuszy?

Podziel się statyczne pole pomiędzy wszystkimi klasami. Kiedyś trzymałem Singletona w pobliżu dla tego i innych rzeczy, które muszą być dzielone. Schemat liczenia (zwykle przy użyciu AtomicInteger) również powinien być używany, aby upewnić się, że nigdy nie zamykasz bazy danych wcześniej lub nie zostawiasz jej otwartej.

Moje rozwiązanie:

Dla najbardziej aktualnej wersji, zobacz https://github.com/JakarCo/databasemanager ale postaram się, aby Kod był aktualny również tutaj. Jeśli chcesz zrozumieć moje rozwiązanie, spójrz na kod i przeczytaj moje notatki. Moje notatki są zazwyczaj bardzo pomocne.

  1. skopiuj / wklej kod do nowego pliku o nazwie DatabaseManager. (lub pobierz go z github)
  2. rozszerz DatabaseManager i zaimplementuj onCreate i onUpgrade jak zwykle. Możesz utworzyć wiele podklas klasy one DatabaseManager, aby mieć różne bazy danych na dysku.
  3. Utwórz instancję swojej podklasy i wywołaj getDb(), aby użyć klasy SQLiteDatabase.
  4. wywołanie close() dla każdej podklasy, którą instantiated

Kod do kopiuj / wklej:

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();
                }
        } 
    }
}
 16
Author: Jakar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-03 23:09:17

Baza danych jest bardzo elastyczna z wielowątkowością. Moje aplikacje trafiają w swoje DBs z wielu różnych wątków jednocześnie i robi to dobrze. W niektórych przypadkach mam wiele procesów uderzających w DB jednocześnie i to działa dobrze też.

Twoje zadania asynchroniczne-używaj tego samego połączenia, gdy możesz, ale jeśli musisz, możesz uzyskać dostęp do bazy danych z różnych zadań.

 10
Author: Brad Hein,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-22 16:14:17

Odpowiedź Dmytro pasuje do mojej sprawy. Myślę, że lepiej zadeklarować funkcję jako zsynchronizowaną. przynajmniej w moim przypadku wywołałoby to wyjątek wskaźnika null, np. getWritableDatabase jeszcze nie zwrócony w jednym wątku i opendatabse wywołany w innym wątku.

public synchronized SQLiteDatabase openDatabase() {
        if(mOpenCounter.incrementAndGet() == 1) {
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
        }
        return mDatabase;
    }
 6
Author: gonglong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-07 09:01:27

Moje zrozumienie API SQLiteDatabase jest takie, że jeśli masz wielowątkową aplikację, nie możesz sobie pozwolić na posiadanie więcej niż 1 obiektu sqlitedatabase wskazującego na pojedynczą bazę danych.

Obiekt definitywnie może zostać utworzony, ale wstawki / aktualizacje nie powiodą się, jeśli różne wątki / procesy (również) zaczną używać różnych obiektów SQLiteDatabase (jak w połączeniu z JDBC).

Jedynym rozwiązaniem tutaj jest trzymanie się 1 obiektów SQLiteDatabase i gdy funkcja startTransaction () jest używana w więcej niż 1 wątku, Android zarządza blokowaniem różnych wątków i pozwala tylko 1 wątkowi na wyłączny dostęp do aktualizacji.

Możesz również zrobić "odczyt" z bazy danych i użyć tego samego obiektu sqlitedatabase w innym wątku (podczas gdy inny wątek pisze) i nigdy nie będzie uszkodzeń bazy danych tzn. "read thread" nie odczyta danych z bazy danych dopóki "write thread" nie zatwierdzi danych, chociaż oba używają tego samego Obiekt SQLiteDatabase.

To różni się od tego, jak obiekt connection jest w JDBC, gdzie jeśli przekażesz (użyjesz tego samego) obiekt connection między wątkami do odczytu i zapisu, prawdopodobnie wydrukujemy również niezakontraktowane dane.

W mojej aplikacji enterprise staram się używać sprawdzeń warunkowych, aby wątek interfejsu nigdy nie musiał czekać, podczas gdy wątek BG przechowuje obiekt sqlitedatabase (wyłącznie). Staram się przewidzieć działania UI i odroczyć BG thread od uruchomienia na ' X ' sekund. Można również zachować PriorityQueue do zarządzania rozdawaniem obiektów połączeń Sqlitedatabase tak, że wątek UI dostaje go pierwszy.

 4
Author: Swaroop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-03 09:35:07

Po kilku godzinach zmagania się z tym odkryłem, że można używać tylko jednego obiektu helpera db Na wykonanie db. Na przykład,

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

Jak przystało na:

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();

Tworzenie nowego Dbadaptera za każdym razem, gdy pętla się powtarza, było jedynym sposobem, w jaki mogłem dostać moje stringi do bazy danych za pośrednictwem mojej klasy pomocniczej.

 4
Author: dell116,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-01 05:50:19

Mając pewne problemy, myślę, że rozumiem, dlaczego źle postępuję.

Napisałem klasę wrappera bazy danych, która zawierała close(), która wywołała Helper close jako mirror open(), która wywołała getWriteableDatabase, a następnie przeniosła się do ContentProvider. Model dla ContentProvider nie używa SQLiteDatabase.close() co myślę, że jest dużą wskazówką, ponieważ kod używa getWriteableDatabase w niektórych przypadkach nadal robiłem bezpośredni dostęp (screen validation queries in the main, więc przeniosłem się do getWriteableDatabase / rawQuery model.

Używam Singletona i jest nieco złowieszczy komentarz w close documentation

Zamknij dowolny otwórz obiekt bazy danych

(moja śmiałość).

Więc miałem przerywane awarie, gdzie używam wątków w tle, aby uzyskać dostęp do bazy danych i działają w tym samym czasie, co na pierwszym planie.

Więc myślę, że close() zmusza bazę danych do zamknięcia niezależnie od innych wątków zawierających odniesienia - więc close() samo w sobie nie jest po prostu cofnięciem dopasowania getWriteableDatabase, ale wymuszeniem zamknięcia dowolne otwarte wnioski. W większości przypadków nie jest to problemem, ponieważ kod jest jednowątkowy, ale w przypadkach wielowątkowych zawsze istnieje szansa na otwarcie i zamknięcie bez synchronizacji.

Po przeczytaniu komentarzy w innym miejscu, które wyjaśniają, że instancja kodu sqlitedatabasehelper liczy się, wtedy jedynym momentem, w którym chcesz zamknąć, jest sytuacja, w której chcesz zrobić kopię zapasową, a Ty chcesz wymusić zamknięcie wszystkich połączeń i wymusić na SqLite zapisanie wszelkich buforowanych rzeczy, które mogą się włóczyć - innymi słowy zatrzymaj całą aktywność bazy danych aplikacji, Zamknij na wypadek, gdyby Pomocnik stracił ścieżkę, wykonaj dowolną aktywność na poziomie plików (kopia zapasowa/przywracanie), a następnie rozpocznij wszystko od nowa.

Chociaż dobrym pomysłem jest próba kontrolowanego zamykania, w rzeczywistości Android zastrzega sobie prawo do kosza maszyny wirtualnej, więc każde zamknięcie zmniejsza ryzyko buforowania aktualizacje nie są zapisywane, ale nie można zagwarantować, że urządzenie jest obciążone, a jeśli poprawnie zwolniłeś kursory i odniesienia do baz danych (które nie powinny być członkami statycznymi), helper i tak zamknie bazę danych.

Więc moim zdaniem podejście jest takie:

Użyj getWriteableDatabase, aby otworzyć z opakowania singleton. (Użyłem pochodnej klasy aplikacji, aby zapewnić kontekst aplikacji ze statycznego, aby rozwiązać potrzebę kontekst).

Nigdy nie zbliżaj się bezpośrednio.

Nigdy nie przechowuj wynikowej bazy danych w żadnym obiekcie, który nie ma oczywistego zakresu i polegaj na zliczaniu referencji, aby wywołać ukrytą metodę close().

Jeśli wykonujesz obsługę na poziomie plików, zatrzymaj całą aktywność bazy danych, a następnie wywołaj close na wypadek, gdyby istniał uciekający wątek, zakładając, że napiszesz odpowiednie transakcje, aby uciekający wątek się nie powiódł, a zamknięta baza danych będzie miała przynajmniej odpowiednie transakcje zamiast potencjalnie kopii częściowej transakcji na poziomie pliku.

 3
Author: Ian Spencer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-03 21:47:44

Możesz spróbować zastosować nowe podejście architektury anunded w Google I/O 2017.

Zawiera również nową bibliotekę ORM o nazwie Room

Zawiera trzy główne komponenty: @ Entity, @ Dao i @Database

Użytkownik.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();
}
 3
Author: Zimbo Rodger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 19:17:59

Wiem, że odpowiedź jest spóźniona, ale najlepszym sposobem na wykonanie zapytań sqlite w Androidzie jest za pośrednictwem niestandardowego dostawcy treści. W ten sposób interfejs użytkownika jest oddzielony od klasy bazy danych (klasy rozszerzającej klasę sqliteopenhelper). Również zapytania są wykonywane w wątku tła (Cursor Loader).

 0
Author: Theo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-24 06:13:11