¿Cuáles son las mejores prácticas para SQLite en Android?

¿Cuáles se considerarían las mejores prácticas al ejecutar consultas en una base de datos SQLite dentro de una aplicación de Android?

¿Es seguro ejecutar inserciones, eliminar y seleccionar consultas desde doInBackground de AsyncTask? ¿O debería usar el hilo de la interfaz de usuario? Supongo que las consultas de la base de datos pueden ser "pesadas" y no deberían usar el subproceso de la interfaz de usuario, ya que puede bloquear la aplicación, lo que da como resultado un La aplicación no responde (ARN).

Si tengo varias AsyncTasks, ¿deberían compartir una conexión o deberían abrir una conexión cada una?

¿Existen mejores prácticas para estos escenarios?

preguntado el 22 de marzo de 10 a las 12:03

Hagas lo que hagas, recuerda desinfectar tus entradas si tu proveedor de contenido (o interfaz SQLite) se enfrenta al público. -

Definitivamente NO deberías estar haciendo accesos a la base de datos desde el hilo de la interfaz de usuario, puedo decirte eso. -

@EdwardFalk ¿Por qué no? Seguramente hay casos de uso en los que es válido hacer esto. -

Si realiza E / S, accesos a la red, etc. desde el subproceso de la interfaz de usuario, todo el dispositivo se congela hasta que se completa la operación. Si se completa en 1/20 de segundo, está bien. Si tarda más, tiene una mala experiencia de usuario. -

10 Respuestas

Las inserciones, actualizaciones, eliminaciones y lecturas generalmente están bien desde múltiples hilos, pero Brad https://www.youtube.com/watch?v=xB-eutXNUMXJtA&feature=youtu.be no es correcto. Tienes que tener cuidado con la forma en que creas tus conexiones y las usas. Hay situaciones en las que sus llamadas de actualización fallarán, incluso si su base de datos no se corrompe.

La respuesta básica.

El objeto SqliteOpenHelper se aferra a una conexión de base de datos. Parece ofrecerle una conexión de lectura y escritura, pero realmente no es así. Llame al modo de solo lectura y obtendrá la conexión de la base de datos de escritura independientemente.

Entonces, una instancia de ayuda, una conexión de base de datos. Incluso si lo usa desde varios subprocesos, una conexión a la vez. El objeto SqliteDatabase utiliza bloqueos Java para mantener el acceso serializado. Por lo tanto, si 100 subprocesos tienen una instancia de base de datos, las llamadas a la base de datos real en el disco se serializan.

Entonces, un ayudante, una conexión de base de datos, que se serializa en código java. Un hilo, 1000 hilos, si usa una instancia auxiliar compartida entre ellos, todo su código de acceso a la base de datos es serial. Y la vida es buena (ish).

Si intenta escribir en la base de datos desde distintas conexiones reales al mismo tiempo, una fallará. No esperará hasta que se haga el primero y luego escribir. Simplemente no escribirá su cambio. Peor aún, si no llama a la versión correcta de insertar / actualizar en SQLiteDatabase, no obtendrá una excepción. Solo recibirá un mensaje en su LogCat, y eso será todo.

Entonces, ¿múltiples hilos? Utilice un ayudante. Período. Si SABE que solo se escribirá un hilo, PUEDE poder usar múltiples conexiones y sus lecturas serán más rápidas, pero tenga cuidado con el comprador. No he probado tanto.

Aquí hay una publicación de blog con muchos más detalles y una aplicación de ejemplo.

Gray y yo estamos terminando una herramienta ORM, basada en su Ormlite, que funciona de forma nativa con implementaciones de bases de datos de Android y sigue la estructura segura de creación / llamada que describo en la publicación del blog. Eso debería salir muy pronto. Echar un vistazo.


Mientras tanto, hay una publicación de blog de seguimiento:

También revisa el tenedor por 2point0 del ejemplo de bloqueo mencionado anteriormente:

Respondido 17 Abr '18, 07:04

Además, el soporte para Android de Ormlite se puede encontrar en ormlite.sourceforge.net/sqlite_java_android_orm.html. Hay proyectos de muestra, documentación y frascos. - Gris

Un segundo aparte. El código ormlite tiene clases auxiliares que se pueden usar para administrar instancias dbhelper. Puede usar las cosas de ormlite, pero no es obligatorio. Puede usar las clases auxiliares solo para administrar la conexión. - kevin galigan

Kāgii, gracias por la explicación detallada. ¿Podría aclarar una cosa? Entiendo que debería tener UN ayudante, pero ¿también debería tener solo una conexión (es decir, un objeto SqliteDatabase)? En otras palabras, ¿con qué frecuencia debe llamar a getWritableDatabase? Y lo que es igualmente importante, ¿cuándo llama a close ()? - Artem

Actualicé el código. El original se perdió cuando cambié de host de blog, pero agregué un código de ejemplo reducido que demostrará el problema. Además, ¿cómo gestiona la conexión única? Inicialmente tuve una solución mucho más complicada, pero desde entonces la he modificado. Eche un vistazo aquí: touchlab.co/uncategorized/single-sqlite-conexión - kevin galigan

¿Es necesario eliminar la conexión y dónde hacerlo? - Tuğçe

Acceso concurrente a la base de datos

Mismo artículo en mi blog (me gusta formatear más)

Escribí un pequeño artículo que describe cómo hacer que el acceso al hilo de su base de datos de Android sea seguro.


Asumiendo que tienes el tuyo propio SQLiteOpenHelper.

public class DatabaseHelper extends SQLiteOpenHelper { ... }

Ahora desea escribir datos en la base de datos en subprocesos separados.

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

Recibirá el siguiente mensaje en su logcat y uno de sus cambios no se escribirá.

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

Esto sucede porque cada vez que crea nuevos SQLiteOpenHelper objeto que en realidad está haciendo una nueva conexión a la base de datos. Si intenta escribir en la base de datos desde distintas conexiones reales al mismo tiempo, una fallará. (de la respuesta anterior)

Para usar una base de datos con varios subprocesos, debemos asegurarnos de que estamos usando una conexión de base de datos.

Hagamos clase singleton Database Manager que aguantará y volverá solo SQLiteOpenHelper objeto.

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

}

El código actualizado que escribe datos en la base de datos en subprocesos separados se verá así.

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

Esto te traerá otro choque.

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

Dado que estamos usando solo una conexión de base de datos, el método getDatabase () devolver la misma instancia de Base de datos SQLite objeto para Hilo1 y Hilo2. Lo que está sucediendo, Hilo1 puede cerrar la base de datos, mientras que Hilo2 todavía lo está usando. Por eso tenemos IllegalStateExceptionIlegalStateException choque.

Necesitamos asegurarnos de que nadie esté usando la base de datos y solo luego cerrarla. Algunas personas de stackoveflow recomendaron no cerrar nunca su Base de datos SQLite. Esto resultará en el siguiente mensaje logcat.

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

Muestra de trabajo

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

        }
    }

}

Úselo de la siguiente manera.

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

Cada vez que necesite una base de datos debe llamar openDatabase () método de Administrador de base de datos clase. Dentro de este método, tenemos un contador, que indica cuántas veces se abre la base de datos. Si es igual a uno, significa que necesitamos crear una nueva conexión a la base de datos, si no, la conexión a la base de datos ya está creada.

Lo mismo pasa en closeDatabase () método. Cada vez que llamamos a este método, el contador disminuye, siempre que llega a cero, cerramos la conexión a la base de datos.


Ahora debería poder usar su base de datos y asegurarse de que sea segura para subprocesos.

Respondido 01 Feb 19, 04:02

Soy una de esas personas que sugiere que nunca cierres la base de datos. Solo obtendrá un error de "Fuga encontrada" si abre la base de datos, no la cierre y luego intente abrirla de nuevo. Si solo usa un ayudante abierto y nunca cierre la base de datos, no obtendrá ese error. Si encuentra lo contrario, hágamelo saber (con el código). Tenía una publicación más larga al respecto en alguna parte, pero no puedo encontrarla. Pregunta respondida por commonsware aquí, que nos supera a ambos en el departamento de puntos SO: stackoverflow.com/questions/7211941/… - kevin galigan

Más pensamientos. # 1, crearía el ayudante dentro de su gerente. Pidiendo problemas para tenerlo afuera. El nuevo desarrollador podría llamar al ayudante directamente por alguna razón loca. Además, si va a requerir un método de inicio, lance una excepción si la instancia ya existe. Las aplicaciones de múltiples bases de datos obviamente fallarán tal como están. # 2, ¿por qué el campo mDatabase? Está disponible en helper. # 3, como su primer paso hacia "nunca cerrar", ¿qué le sucede a su base de datos cuando su aplicación falla y no está "cerrada"? Pista, nada. Está bien, porque SQLite es súper estable. Ese fue el primer paso para descubrir el porqué no es necesario cerrarlo. - kevin galigan

¿Alguna razón por la que usa un método de inicialización público para llamar antes de obtener la instancia? ¿Por qué no tener un constructor privado que se llame if(instance==null)? No deja más remedio que llamar a initialize cada vez; ¿De qué otra manera sabría si se ha inicializado o no en otras aplicaciones, etc.? - JefeDosLápices

initializeInstance() tiene un parámetro de tipo SQLiteOpenHelper, pero en tu comentario mencionaste usar DatabaseManager.initializeInstance(getApplicationContext());. Que esta pasando? ¿Cómo es posible que esto funcione? - Faizal

@DmytroDanylyk "DatabaseManager es singleton seguro para subprocesos, por lo que podría usarse en cualquier lugar", eso no es cierto en respuesta a la pregunta de virsir. Los objetos no son procesos cruzados compartidos. Su DatabaseManager no tendrá estado en un proceso diferente como en un adaptador de sincronización (android: process = ": sync") - silbido

  • Utilizar Thread or AsyncTask para operaciones de larga duración (50ms +). Pruebe su aplicación para ver dónde está. La mayoría de las operaciones (probablemente) no requieren un hilo, porque la mayoría de las operaciones (probablemente) solo involucran unas pocas filas. Utilice un hilo para operaciones masivas.
  • Compartir uno SQLiteDatabase instancia para cada base de datos en el disco entre subprocesos e implementar un sistema de conteo para realizar un seguimiento de las conexiones abiertas.

¿Existen mejores prácticas para estos escenarios?

Comparta un campo estático entre todas sus clases. Solía ​​tener un singleton para eso y otras cosas que necesitan ser compartidas. También debe usarse un esquema de conteo (generalmente usando AtomicInteger) para asegurarse de que nunca cierre la base de datos antes de tiempo o la deje abierta.

Mi solución:

La versión anterior que escribí está disponible en https://github.com/Taeluf/dev/tree/main/archived/databasemanager y no se mantiene. Si quieres entender mi solución, mira el código y lee mis notas. Mis notas suelen ser muy útiles.

  1. copiar / pegar el código en un nuevo archivo llamado DatabaseManager. (o descárgalo desde github)
  2. ampliar DatabaseManager e implementar onCreate y onUpgrade como lo haría normalmente. Puede crear varias subclases de una DatabaseManager class para tener diferentes bases de datos en el disco.
  3. Crea una instancia de tu subclase y llama getDb() utilizar el SQLiteDatabase clase.
  4. Llame a close() para cada subclase que instanciaste

El código para copiar / pegar:

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 support@androidsqlitelibrary.com (or support@jakar.co )
 * 
 * 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();
                }
        } 
    }
}

Respondido 15 Oct 20, 15:10

¿Qué sucede cuando llamas "cerrar" y luego intentas reutilizar la clase? ¿Se estrellará? ¿O se reinicializará automáticamente para poder usar la base de datos nuevamente? - desarrollador de Android

@androiddeveloper, si llamas closetienes que llamar open nuevamente antes de usar la misma instancia de la clase O puede crear una nueva instancia. Ya que en el close código, configuro db=null, no podría utilizar el valor de retorno de getDb (ya que sería nulo), por lo que obtendría un NullPointerException si hicieras algo como myInstance.close(); myInstance.getDb().query(...); - Caña

¿Por qué no combinar getDb() y open() en un solo método? - Alex Burdusel

@Burdu, una combinación de proporcionar control adicional sobre el contador de la base de datos y un mal diseño. Sin embargo, definitivamente no es la mejor manera de hacerlo. Lo actualizaré en unos días. - Caña

@Burdu, lo acabo de actualizar. Puede obtener el nuevo código de aquí. No lo he probado, así que avíseme si debo realizar los cambios. - Caña

La base de datos es muy flexible con subprocesos múltiples. Mis aplicaciones acceden a sus bases de datos de muchos subprocesos diferentes simultáneamente y funciona bien. En algunos casos, tengo varios procesos que golpean la base de datos simultáneamente y eso también funciona bien.

Sus tareas asincrónicas: use la misma conexión cuando pueda, pero si es necesario, está bien acceder a la base de datos desde diferentes tareas.

respondido 22 mar '10, 16:03

Además, ¿tiene lectores y escritores en diferentes conexiones o deberían compartir una única conexión? Gracias. - Gris

@ Gray: correcto, debería haberlo mencionado explícitamente. En cuanto a las conexiones, usaría la misma conexión tanto como sea posible, pero dado que el bloqueo se maneja a nivel del sistema de archivos, puede abrirlo varias veces en el código, pero usaría una sola conexión tanto como sea posible. La base de datos sqlite de Android es muy flexible y tolerante. - brad hein

@Gray, solo quería publicar información actualizada para las personas que pueden estar usando este método. La documentación dice: Este método ahora no hace nada. No utilice. - Pijusn

Descubrí que este método falla terriblemente, estamos cambiando a ContentProvider para acceder desde múltiples aplicaciones. Tendremos que hacer algo de simultaneidad en nuestros métodos, pero eso debería solucionar cualquier problema con los procesos que acceden a los datos al mismo tiempo. - JPM

Sé que esto es antiguo, pero es incorrecto. Eso podría funciona bien para acceder a la base de datos desde diferentes SQLiteDatabase objetos en diferentes AsyncTasks/Threads, pero a veces dará lugar a errores, por lo que Base de datos SQLite (línea 1297) utiliza Locks - Caña

después de luchar con esto durante un par de horas, descubrí que solo puede usar un objeto auxiliar de db por ejecución de db. Por ejemplo,

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

en oposición a:

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

crear un nuevo DBAdapter cada vez que el bucle itera era la única forma en que podía introducir mis cadenas en una base de datos a través de mi clase auxiliar.

Respondido 01 Jul 11, 06:07

La respuesta de Dmytro funciona bien para mi caso. Creo que es mejor declarar la función sincronizada. al menos para mi caso, invocaría una excepción de puntero nulo de lo contrario, por ejemplo, getWritableDatabase aún no se ha devuelto en un hilo y openDatabse se llama en otro hilo mientras tanto.

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

respondido 12 mar '20, 10:03

mDatabaseHelper.getWritableDatabase (); Esto no creará un nuevo objeto de base de datos - periférico

Mi comprensión de las API de SQLiteDatabase es que, en caso de que tenga una aplicación de subprocesos múltiples, no puede permitirse tener más de un objeto SQLiteDatabase apuntando a una sola base de datos.

El objeto definitivamente se puede crear, pero las inserciones / actualizaciones fallan si diferentes subprocesos / procesos (también) comienzan a usar diferentes objetos SQLiteDatabase (como la forma en que usamos en JDBC Connection).

La única solución aquí es quedarse con 1 objeto SQLiteDatabase y cada vez que se usa un startTransaction () en más de 1 subproceso, Android administra el bloqueo en diferentes subprocesos y permite que solo 1 subproceso a la vez tenga acceso de actualización exclusivo.

También puede hacer "lecturas" de la base de datos y usar el mismo objeto SQLiteDatabase en un hilo diferente (mientras que otro hilo escribe) y nunca habrá corrupción en la base de datos, es decir, "hilo de lectura" no leería los datos de la base de datos hasta el " write thread "confirma los datos aunque ambos usan el mismo objeto SQLiteDatabase.

Esto es diferente de cómo es el objeto de conexión en JDBC, donde si pasa (usa el mismo) el objeto de conexión entre los subprocesos de lectura y escritura, es probable que también imprimamos datos no confirmados.

En mi aplicación empresarial, trato de usar verificaciones condicionales para que el subproceso de la interfaz de usuario nunca tenga que esperar, mientras que el subproceso BG contiene el objeto SQLiteDatabase (exclusivamente). Intento predecir las acciones de la interfaz de usuario y aplazar la ejecución del hilo de BG durante 'x' segundos. También se puede mantener PriorityQueue para administrar la distribución de objetos de conexión SQLiteDatabase para que el subproceso de la interfaz de usuario lo obtenga primero.

Respondido 03 Feb 11, 09:02

¿Y qué pones en ese PriorityQueue: oyentes (que quieren obtener el objeto de la base de datos) o consultas SQL? - Pijusn

No he usado el enfoque de la cola de prioridad, sino esencialmente los hilos de "llamador". - Swaroop

@Swaroop: PCMIIW,"read thread" wouldn't read the data from the database till the "write thread" commits the data although both use the same SQLiteDatabase object. Esto no siempre es cierto, si inicia "leer hilo" justo después de "escribir hilo", es posible que no obtenga los datos recién actualizados (insertados o actualizados en el hilo de escritura). El hilo de lectura puede leer los datos antes del inicio del hilo de escritura. Esto sucede porque la operación de escritura habilita inicialmente el bloqueo reservado en lugar del bloqueo exclusivo. - Amit Vikram Singh

Puede intentar aplicar un nuevo enfoque de arquitectura anunciada en Google I / O 2017.

También incluye una nueva biblioteca ORM llamada Habitación

Contiene tres componentes principales: @Entity, @Dao y @Database

Usuario.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.
}

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

contestado el 23 de mayo de 17 a las 20:05

No sugiero Room to a Database con múltiples relaciones N-to-N, porque no maneja bien este enfoque y tiene que escribir mucho código para encontrar una solución para estas relaciones. - Alex Pad

Habiendo tenido algunos problemas, creo que he entendido por qué me he equivocado.

Había escrito una clase contenedora de base de datos que incluía un close() que llamó al ayudante cerca como un espejo de open() que llamó a getWriteableDatabase y luego migró a un ContentProvider. El modelo para ContentProvider no utilizar SQLiteDatabase.close() que creo que es una gran pista ya que el código usa getWriteableDatabase En algunos casos, todavía estaba haciendo acceso directo (consultas de validación de pantalla en la principal, así que migré a un modelo getWriteableDatabase / rawQuery.

Utilizo un singleton y hay un comentario ligeramente ominoso en la documentación cercana

Cerrar cualquier objeto de base de datos abierta

(mi negrita).

Así que he tenido bloqueos intermitentes en los que utilizo subprocesos en segundo plano para acceder a la base de datos y se ejecutan al mismo tiempo que en primer plano.

Por eso pienso close() obliga a la base de datos a cerrarse independientemente de cualquier otro hilo que contenga referencias, por lo que close() en sí mismo no es simplemente deshacer la coincidencia getWriteableDatabase pero forzar el cierre cualquier solicitudes abiertas. La mayoría de las veces, esto no es un problema, ya que el código es de un solo subproceso, pero en los casos de subprocesos múltiples siempre existe la posibilidad de que se abra y se cierre sin sincronizar.

Después de haber leído los comentarios en otra parte que explican que la instancia del código SqLiteDatabaseHelper cuenta, entonces la única vez que desea un cierre es donde desea la situación en la que desea hacer una copia de respaldo, y desea forzar que todas las conexiones se cierren y forzar a SqLite a anote cualquier cosa almacenada en caché que pueda estar merodeando; en otras palabras, detenga toda la actividad de la base de datos de la aplicación, cierre en caso de que el Asistente haya perdido la pista, realice cualquier actividad a nivel de archivo (copia de seguridad / restauración) y luego comience de nuevo.

Aunque suena como una buena idea intentar cerrar de forma controlada, la realidad es que Android se reserva el derecho de tirar a la basura su VM, por lo que cualquier cierre reduce el riesgo de que las actualizaciones en caché no se escriban, pero no se puede garantizar si el dispositivo está estresado, y si ha liberado correctamente los cursores y las referencias a las bases de datos (que no deberían ser miembros estáticos), el asistente habrá cerrado la base de datos de todos modos.

Entonces mi opinión es que el enfoque es:

Utilice getWriteableDatabase para abrir desde un contenedor singleton. (Usé una clase de aplicación derivada para proporcionar el contexto de la aplicación desde una estática para resolver la necesidad de un contexto).

Nunca llame directamente a cerrar.

Nunca almacene la base de datos resultante en ningún objeto que no tenga un alcance obvio y confíe en el recuento de referencias para desencadenar un cierre implícito ().

Si realiza el manejo a nivel de archivo, detenga toda la actividad de la base de datos y luego llame al cierre en caso de que haya un hilo fuera de control asumiendo que escribe transacciones adecuadas para que el hilo fuera de control fallará y la base de datos cerrada al menos tendrá transacciones adecuadas en su lugar. que potencialmente una copia a nivel de archivo de una transacción parcial.

respondido 03 mar '17, 21:03

Sé que la respuesta llega tarde, pero la mejor manera de ejecutar consultas sqlite en Android es a través de un proveedor de contenido personalizado. De esa manera, la interfaz de usuario se desacopla con la clase de la base de datos (la clase que extiende la clase SQLiteOpenHelper). Además, las consultas se ejecutan en un hilo de fondo (Cursor Loader).

respondido 24 nov., 17:06

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.