web-dev-qa-db-fra.com

Android threading et verrouillage de la base de données

Nous utilisons AsyncTasks pour accéder aux tables de base de données et aux curseurs.

Malheureusement, nous voyons des exceptions occasionnelles concernant le verrouillage de la base de données.

E/SQLiteOpenHelper(15963): Couldn't open iviewnews.db for writing (will try read-only):
E/SQLiteOpenHelper(15963): Android.database.sqlite.SQLiteException: database is locked
E/SQLiteOpenHelper(15963):  at     Android.database.sqlite.SQLiteDatabase.native_setLocale(Native Method)
E/SQLiteOpenHelper(15963):  at     Android.database.sqlite.SQLiteDatabase.setLocale(SQLiteDatabase.Java:1637)
E/SQLiteOpenHelper(15963):  at     Android.database.sqlite.SQLiteDatabase.<init>(SQLiteDatabase.Java:1587)
E/SQLiteOpenHelper(15963):  at Android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.Java:638)
E/SQLiteOpenHelper(15963):  at Android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.Java:659)
E/SQLiteOpenHelper(15963):  at Android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.Java:652)
E/SQLiteOpenHelper(15963):  at Android.app.ApplicationContext.openOrCreateDatabase(ApplicationContext.Java:482)
E/SQLiteOpenHelper(15963):  at Android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.Java:193)
E/SQLiteOpenHelper(15963):  at Android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.Java:98)
E/SQLiteOpenHelper(15963):  at Android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.Java:158)
E/SQLiteOpenHelper(15963):  at com.iview.Android.widget.IViewNewsTopStoryWidget.initData(IViewNewsTopStoryWidget.Java:73)
E/SQLiteOpenHelper(15963):  at com.iview.Android.widget.IViewNewsTopStoryWidget.updateNewsWidgets(IViewNewsTopStoryWidget.Java:121)
E/SQLiteOpenHelper(15963):  at com.iview.Android.async.GetNewsTask.doInBackground(GetNewsTask.Java:338)
E/SQLiteOpenHelper(15963):  at com.iview.Android.async.GetNewsTask.doInBackground(GetNewsTask.Java:1)
E/SQLiteOpenHelper(15963):  at Android.os.AsyncTask$2.call(AsyncTask.Java:185)
E/SQLiteOpenHelper(15963):  at Java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.Java:256)
E/SQLiteOpenHelper(15963):  at Java.util.concurrent.FutureTask.run(FutureTask.Java:122)
E/SQLiteOpenHelper(15963):  at Java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.Java:648)
E/SQLiteOpenHelper(15963):  at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:673)
E/SQLiteOpenHelper(15963):  at Java.lang.Thread.run(Thread.Java:1060)

Quelqu'un at-il un exemple général de code qui écrit dans une base de données à partir d'un thread différent de celui qui lit et comment pouvons-nous assurer la sécurité des threads?.

Une suggestion que j'ai eue est d'utiliser un ContentProvider, car cela gérerait l'accès à la base de données à partir de plusieurs threads. Je vais regarder cela, mais est-ce la méthode recommandée pour gérer un tel problème? Cela semble plutôt lourd étant donné que nous parlons devant ou derrière.

28
Pandalover

J'ai résolu cette même exception juste en m'assurant que toutes les ouvertures de ma base de données se sont fermées et (plus important) pour le garantir, en rendant la portée de chaque instance de base de données locale UNIQUEMENT pour la méthode qui en a besoin. ContentProvider est une bonne classe sûre à utiliser lors de l'accès à une base de données à partir de plusieurs threads, mais assurez-vous également que vous utilisez de bonnes pratiques de base de données:

  • Gardez les instances db locales (pas de membres de classe SQLiteDatabase!)
  • appeler close() sur la base de données dans la même méthode que celle dans laquelle elle est ouverte
  • appeler close() sur les curseurs que vous obtenez à partir de la base de données
  • écouter LogCat pour toute plainte que SQLiteDatabse pourrait avoir
16
ubzack

Nous avons utilisé un ContentProvider à la fin. Cela semblait éclaircir les problèmes.

28
Pandalover

Avant de coder, reprenons certaines des approches:

  • Sémaphores: de loin la meilleure solution présentée. Cela va au cœur du problème: le partage des ressources! Il traitera le verrouillage de l'accès à la base de données, en évitant les conflits (database is locked).

  • synchronisation Java: Une sorte d'implémentation de sémaphore, mais moins sophistiquée. En utilisant synchronized, vous ne résoudrez pas facilement certains cas impliquant des transactions.

  • ContentProvider: implémenter ContentProvider résoudre le problème uniquement dans certains cas (ou balayer le problème sous le tapis) . Vous rencontrerez pourtant les mêmes problèmes. La différence est que le modèle ContentProvider vous aidera à ne pas commettre d'erreurs commom lors de l'accès à la base de données Sqlite. Documents ContentProvider dit: "Vous n'avez pas besoin d'un fournisseur pour utiliser une base de données SQLite si l'utilisation se fait entièrement dans votre propre application."

  • Presque obligatoire: gardez les instances de base de données locales, appelez close() sur la base de données de la même manière que il est ouvert en utilisant les instructions finally, close() sur les curseurs en utilisant les instructions finally, etc. sont presque obligatoire pour éviter les problèmes avec Sqlite.

Montrons un exemple de la solution de sémaphore présentée par Moss , que j'ai prise de CL. et améliorée pour couvrir les transactions.

class DataAccess {
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Data readSomething(int id) {
        Cursor c = null;
        r.lock();
        try {
            c = getReadableDatabase().query(...);
            return c.getString(0);
        } finally {
            if (c != null) c.close();
            r.unlock();
        }
    }

    public void changeSomething(int id, int value) {
        w.lock();
        try {
            getWritableDatabase().update(...);
        } finally {
            w.unlock();
        }
    }

    private void beginTransactionWithSemaphores() {
        getWritableDatabase().beginTransactionWithListener(new SQLiteTransactionListener() {
            @Override
            public void onBegin() {
                w.lock();
            }

            @Override
            public void onRollback() {
                w.unlock();
            }

            @Override
            public void onCommit() {
                w.unlock();
            }
        });
    }
}
11
Italo Borssatto

Tenez compte du fait que les bases de données SQLite sont basées sur des fichiers et ne sont pas destinées à être accessibles de manière multi-processus. La meilleure procédure pour mélanger SQLite et le multi-traitement consiste à utiliser des sémaphores (aquire (), release ()) dans chaque accès lié à la base de données.

Si vous créez un wrapper Db qui acquiert/libère un sémaphore global, votre accès DB sera thread-safe. En effet, cela signifie que vous pourriez obtenir un goulot d'étranglement car vous mettez en file d'attente l'accès à la base de données. Donc, en plus, vous ne pouvez envelopper l'accès à l'aide de sémaphores que si c'est une opération qui modifie la base de données, donc pendant que vous modifiez la base de données, personne ne pourra y accéder et attendre la fin du processus d'écriture.

9
Moss

Nous ne pourrions pas partager la connexion Db avec plusieurs threads pour effectuer simultanément des opérations de lecture et d'écriture dans la base de données.Nous devrons créer un seul objet de la base de données en utilisant le concept de synchronisation et nous effectuerons une tâche à la fois. objet et il sera partagé dans plusieurs threads. À un moment donné effectuera une seule tâche. alors nous allons commencer une autre tâche ou toute opération sur DB. Le fournisseur de contenu n'est pas la solution du problème de verrouillage de base de données.

import Java.util.concurrent.atomic.AtomicInteger;
import Android.database.sqlite.SQLiteDatabase;
import Android.database.sqlite.SQLiteOpenHelper;
import Android.util.Log;

public class DatabaseManager {

private AtomicInteger mOpenCounter = new AtomicInteger();

private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
//private static String DB_PATH = "";
//  private static String DB_NAME = "xyz.db";// Database name
private static String dbPathh;

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

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(String thread) {

    if(mOpenCounter.get() == 0) {
        // Opening new database
        // mDatabase = mDatabaseHelper.getWritableDatabase();
        MyLog.e("Path Of DataBase", dbPathh);
        //  mDatabase=mDatabaseHelper.getWritableDatabase();
        mOpenCounter.incrementAndGet();
        mDatabase=SQLiteDatabase.openDatabase(dbPathh, null,   
 SQLiteDatabase.  CREATE_IF_NECESSARY|SQLiteDatabase.OPEN_READWRITE);   
        MyLog.e("Open Data Base", " New Connection created" +thread);
    }
    else{
        MyLog.e("Open Data Base", " Old Connection given " +thread);
    }
    //  Toast.makeText(NNacres.getConfig(), "open conn: present connection = 
   "   +mOpenCounter.get(), Toast.LENGTH_LONG).show();
    return mDatabase;
   }

    public synchronized void closeDatabase() {
    MyLog.e("Close db connection", ""+mOpenCounter.get());

    if(mOpenCounter.get() == 1) {
        // Closing database

        mDatabase.close();
        mOpenCounter.decrementAndGet();

        Log.e("DB CLOSED", "DONE");
    }
    //Toast.makeText(NNacres.getConfig(), "close conn: after close =   
 " +mOpenCounter.get(), Toast.LENGTH_LONG).show();
    }

    }

et écrivez cette méthode dans votre classe d'assistance YourSQLiteDataABse qui étend la classe SQLiteOpenHelper

     public SQLiteDatabase getWritableDatabase() {
DatabaseManager.initializeInstance(this,"data/data/your packgae name/databases/xyz");
    return DatabaseManager.getInstance().openDatabase(getClass().getSimpleName());

}



public static String getMyDbPath(String DB_NAME, Context context) {

    String myDbPath = context.getDatabasePath(DB_NAME).getPath();
    MyLog.e("DB Path: "+myDbPath);
    return myDbPath;
}
1
Ashish Saini