2013-07-18 22 views
7

Sto cercando di ottenere un modello che non fallisce per un accesso multithread al mio database SQLite. Inoltre, quello che mi sta facendo impazzire è che non riesco a riprodurre il problema.Database SQLite, multithreading, serrature e sincronizzazione dell'account su Android

Ho un'app che utilizza un DB, ma anche account Android e sincronizzazione Android per sincronizzare i dati della mia app. La mia ipotesi è che quando i due accadono contemporaneamente, si blocca. Sto ricevendo un sacco di errori come:

* android.database.sqlite.SQLiteDatabaseLockedException: database is locked 
* android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5) 
* android.database.sqlite.SQLiteDatabaseLockedException: error code 5: database is locked 
* android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: PRAGMA journal_mode 
* android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 778) 
* android.database.sqlite.SQLiteException: Failed to change locale for db '/data/data/net.bicou.redmine/databases/redmine.db' to 'en_US'. \n Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5) 

Forse non tutti sono legati alla stessa causa principale, tuttavia Sono un po 'perso.

Quello che ho è:

  • una classe base astratta, DbAdapter, che si estende dalle sottoclassi che vogliono gestire una singola tabella
  • una classe che gestisce il database SQLite, chiamato DbManager, che contiene a Lock

In questo momento gli utenti dispongono di una versione di DbManager che non è un singleton. Sto pianificando di rendere DbManager un singleton, in modo che tutti i thread condividano lo stesso oggetto. Questo non dovrebbe essere un problema, perché per quanto ho capito/visto, la sincronizzazione in background e l'app condividono lo stesso processo.

Ecco le classi (solo le parti rilevanti):

public abstract class DbAdapter { 
    Context mContext; 
    protected DbManager mDbManager; 
    SQLiteDatabase mDb; 

    public static final String KEY_ROWID = "_id"; 

    public DbAdapter(final Context ctx) { 
     mContext = ctx; 
    } 

    public DbAdapter(final DbAdapter other) { 
     mContext = other.mContext; 
     mDb = other.mDb; 
     mDbManager = other.mDbManager; // removed with singleton version 
    } 

    public synchronized DbAdapter open() throws SQLException { 
     if (mDb != null) { 
      return this; 
     } 

     mDbManager = new DbManager(mContext); // currently in production 
     mDbManager = DbManager.instance(mContext); // currently investigating this singleton solution 
     try { 
      mDb = mDbManager.getWritableDatabase(); 
     } catch (final SQLException e) { 
      L.e("Unable to open DB, trying again in 1 second", e); 
      try { 
       Thread.sleep(1000); 
      } catch (final InterruptedException e1) { 
       L.e("Could not wait 1 second " + e1); 
      } 
      mDb = mDbManager.getWritableDatabase();// This may crash 
     } 

     return this; 
    } 

    public synchronized void close() { 
     mDbManager.close(); 
     mDbManager = null; 
     mDb = null; 
    } 
} 

Una classe che deve gestire una tabella di database si estenderà DbAdapter, e implementare metodi come select, insert, delete, ecc

Ecco il manager DB:

public class DbManager extends SQLiteOpenHelper { 
    private static final String DB_FILE = "db"; 
    private static final int DB_VERSION = 15; 
    Context mContext; 
    Lock mLock = new ReentrantLock(); 

    // Currently in prod 
    public DbManager(final Context context) { 
     super(context, DB_FILE, null, DB_VERSION); 
     mContext = context; 
    } 

    // singleton version will make this constructor private and add: 
    private static DbManager mInstance; 
    public static synchronized DbManager instance(Context context) { 
     if (instance == null) { 
      instance = new DbManager(context); 
     } 
     return instance; 
    } 

    @Override 
    public SQLiteDatabase getWritableDatabase() { 
     mLock.lock(); 
     return super.getWritableDatabase(); 
    } 

    @Override 
    public void close() { 
     super.close(); 
     mLock.unlock(); 
    } 

    @Override 
    public void onCreate(final SQLiteDatabase db) { 
     // ... 
    } 

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

    private void createTables(final SQLiteDatabase db, final String[] statements) { 
     for (final String sql : statements) { 
      try { 
       db.execSQL(sql); 
      } catch (final Exception e) { 
       L.e("Unable to create table: " + sql, e); 
      } 
     } 
    } 
} 

OK, ora, le domande.

  1. Il blocco è stato implementato correttamente? Sono davvero nuovo a questo, non so se il ReentrantLock è una buona scelta, e se sto bloccando/sblocco al momento giusto
  2. Il mio metodo synchronized è correttamente implementato? Voglio dire, ho inserito la parola chiave synchronized attorno a metodi che non desidero essere interrotti da thread simultanei. È giusto? Potete consigliarmi sull'uso di synchronized?
  3. Come posso riprodurre il problema? Ho creato un test che utilizza 3 thread che consentono l'accesso simultaneo in lettura/scrittura al DB e utilizza alcuni Thread.sleep per garantire che il db open/close da ogni thread si sovrapponga, ma non si arresti in modo anomalo. Questo mi disturba davvero, non penso che ci sia un sacco di gente che ha il problema, quindi non so come riprodurlo.
  4. La mia scelta tecnica DbAdapter + DbManager è una buona idea? C'è uno schema migliore?
  5. È una buona idea rendere DbManager un singleton?

risposta

0

Per l'accesso a più thread, è consigliabile utilizzare il modello di singleton.

In questo modo, le chiamate successive allo stesso database verranno serializzate senza interruzioni.

Tuttavia, non è impossibile avere alcuni NullPointerException s su inserti. Quindi, per espandere la tua logica "Thread.sleep", potresti usare questo codice:

@Override 
public SQLiteDatabase getWritableDatabase() { 
    while (true) { 
     try { 
      return super.getWritableDatabase(); 
     } catch (SQLiteDatabaseLockedException e) { 
      System.err.println(e); 
     } 

     try { 
      Thread.sleep(500); 
     } catch (InterruptedException e) { 
      System.err.println(e); 
     } 
    } 
} 
Problemi correlati