Domanda

Stiamo usando AsyncTasks alle tabelle del database di accesso e cursori.

Purtroppo stiamo assistendo a sporadiche eccezioni per quanto riguarda il database viene bloccato.

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)

Qualcuno ha un esempio generale per il codice che scrive a un database da un thread diverso da quello di lettura e come possiamo garantire la sicurezza thread.

Un suggerimento che ho avuto è quella di utilizzare un ContentProvider, in quanto ciò avrebbe gestito l'accesso del database da più thread. Ho intenzione di guardare a questo, ma è questo il metodo consigliato di gestire un tale problema? Sembra piuttosto pesante considerando che stiamo parlando davanti o dietro.

È stato utile?

Soluzione

ho risolto questa stessa eccezione solo facendo in modo che tutta la mia base di dati si apre avere chiude, e (soprattutto) per assicurare questo, rendendo l'ambito di ogni istanza di database locali soltanto il metodo che ne ha bisogno. ContentProvider è un bene, di classe sicuro da usare quando si accede a un db da più thread, ma anche fare in modo che si sta utilizzando le buone pratiche db:

  • Mantenere db istanze locali (non membri della classe SQLiteDatabase!)
  • chiamata close() sul db con lo stesso metodo in cui è aperto
  • chiamata close() sui cursori che si ottiene dal db
  • ascoltare LogCat per eventuali reclami che potrebbe avere SQLiteDatabse

Altri suggerimenti

Abbiamo usato un ContentProvider alla fine. Questo è apparso per chiarire i problemi.

Prima del codice, riprendiamo alcuni dei approachs:

  • semafori : di gran lunga la migliore soluzione presentata. Si va nel cuore del problema: la condivisione delle risorse! Si tratterà il bloccaggio della accesso al database, evitando i conflitti (database is locked).

  • Java sincronizzazione : un tipo di implementazione del semaforo, ma meno sofisticato. Utilizzando synchronized non sarà facile risolvere alcuni casi di transazioni.

  • ContentProvider : implementare ContentProvider risolvere il problema solo per alcuni casi (o spazzare il problema sotto il tappeto). Potrai ancora affrontare gli stessi problemi. La differenza è che modello ContentProvider vi guiderà a non fare alcuni errori commom quando si accede a database SQLite. Il ContentProvider docs dice: "Tu non lo fai bisogno di un fornitore di utilizzare un database SQLite se l'uso è del tutto all'interno della vostra applicazione ".

  • quasi obbligatorio : le istanze db keep locale, close() chiamata sul db nello stesso metodo in cui è aperto con le dichiarazioni finally, close() sui cursori utilizzando le istruzioni finally, ecc sono quasi obbligatorie per evitare problemi con SQLite.

Facciamo vedere un esempio della soluzione del semaforo presentato da Moss , che ho preso da CL. e improoved alle operazioni di copertura.

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

Prendere in considerazione che i database SQLite sono file in base e non sono destinati ad essere in grado di accedere in modo multi-processo. La procedura migliore miscelazione SQLite con il multi-processing sta usando semafori (Aquire (), release ()) in ogni accesso relativi database.

Se si crea un DB wrapper che acquisisce / rilascia un semaforo globale l'accesso DB sarà thread-safe. In effetti, questo significa che si potrebbe ottenere un bootleneck perché si sta facendo la coda l'accesso al DB. Quindi, in aggiunta si potrebbe avvolgere solo l'accesso utilizzando i semafori se si tratta di un'operazione che altera il database, quindi mentre si è alterin il db nessuno sarà in grado di accedere e attendere fino a quando il processo di scrittura è stato completato.

Non è stato possibile condividere la connessione Db con filettatura a più eseguire operazioni di lettura e un'operazione di scrittura nel database simultaniously.We dovrà fare singolo oggetto di DB usando il concetto di sincronizzazione e noi eseguire un compito alla volta .We utilizzare pattern Singleton per rendere l'oggetto DB e sarà quota all'interno di più threads.At volta eseguirà singola attività. allora inizieremo altra attività o le operazioni su DB. Content provider non è la soluzione del problema di DB di bloccaggio.

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

    }

e scrivere questo metodo nella classe helper YourSQLiteDataABse che si estende SQLiteOpenHelper Classe

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

Si deve essere chiamando getWritableDatabase() da una funzione piuttosto che il costruttore della classe db aiutante. Se l'oggetto classe helper db viene creato con SQLiteDatabase.openOrCreateDatabase(DB_PATH, null); o simili e poi getWritableDatabase() viene chiamato da una funzione, cercherà di effettuare una chiamata sincrona a DB causando un'eccezione di blocco DB.

Stai parlando di una singola azione utente che, all'interno del vostro programma, fa sì che più thread per essere eseguito, più di uno dei quali potrebbe essere l'accesso al database in modalità di aggiornamento?

Questo è cattiva progettazione, punto. Non c'è modo per voi di conoscere in quale ordine i fili saranno programmate per il vostro sistema operativo (/ VM), e quindi non v'è alcun modo per sapere in quale ordine gli accessi di database accadrà, e che è molto probabile che implicare che non v'è alcun modo per voi di sapere che accede di database sempre accadere nel modo che vi aspettate.

Tutti i database di accessi generati da / provenienti da una certa azione utente deve essere fatto in un unico thread.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top