Ainsi, dans de rares cas, je vois le message "tentative d'écrire une base de données en lecture seule", et je ne peux pas comprendre où se situe le problème. Je vais commencer par le stacktrace dans mon logcat ... comme vous pouvez le voir sur l'horodatage, je vérifie db.isReadOnly () seulement 1 ms avant de tenter l'écriture. (isOpen = true, readOnly = false)
01-29 13:47:49.115: D/AWT(11055): #479.Got writable database (230537815): isOpen: (true) isReadOnly: (false) inTransaction: (false)
01-29 13:47:49.116: D/AWT(11055): #479.in transaction: Got writable database (230537815): isOpen: (true) isReadOnly: (false) inTransaction: (true)
01-29 13:47:49.116: E/SQLiteLog(11055): (1032) statement aborts at 15: [INSERT INTO Events(col1,col2,col3,col4) VALUES (?,?,?,?)]
01-29 13:47:49.117: E/SQLiteDatabase(11055): Error inserting data="scrubbed"
01-29 13:47:49.117: E/SQLiteDatabase(11055): Android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032)
01-29 13:47:49.117: E/SQLiteDatabase(11055): at Android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
01-29 13:47:49.117: E/SQLiteDatabase(11055): at Android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.Java:780)
01-29 13:47:49.117: E/SQLiteDatabase(11055): at Android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.Java:788)
01-29 13:47:49.117: E/SQLiteDatabase(11055): at Android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.Java:86)
01-29 13:47:49.117: E/SQLiteDatabase(11055): at Android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.Java:1471)
01-29 13:47:49.117: E/SQLiteDatabase(11055): at Android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.Java:1341)
01-29 13:47:49.117: E/SQLiteDatabase(11055): at com.company.DbHelper.insertBatch(EventsDbHelper.Java:174)
01-29 13:47:49.117: D/AWT(11055): #479.finalizing transaction: Got writable database (230537815): isOpen: (true) isReadOnly: (false) inTransaction: (true)
01-29 13:47:49.118: W/SQLiteLog(12120): (28) file unlinked while open: /data/user/0/com.company.app/databases/MyDatabase.db
De ma source:
public void insertBatch(LinkedList<WriteQueue.DatabaseRecord> writeQueue) throws Exception {
Log.d("AWT", "EventsDbHelper->insertBatch()");
if (writeQueue == null) {
return;
}
Iterator<DatabaseRecord> it = writeQueue.iterator();
SQLiteDatabase db = this.getWritableDatabase();
Log.d("AWT", String.format("Got writable database (%s): isOpen: (%s) isReadOnly: (%s) inTransaction: (%s)",
db.hashCode(), db.isOpen(), db.isReadOnly(), db.inTransaction()));
try {
db.beginTransaction();
while (it.hasNext()) {
DatabaseRecord record = it.next();
ContentValues initialValues = new ContentValues();
initialValues.put(col1, val1);
initialValues.put(col2, val2);
initialValues.put(col3, val3);
initialValues.put(col4, val4);
Log.d("AWT", String.format("in transaction: Got writable database (%s): isOpen: (%s) isReadOnly: (%s) inTransaction: (%s)",
db.hashCode(), db.isOpen(), db.isReadOnly(), db.inTransaction()));
db.insert(DBTBL, null, initialValues);
}
Log.d("AWT", String.format("finalizing transaction: Got writable database (%s): isOpen: (%s) isReadOnly: (%s) inTransaction: (%s)",
db.hashCode(), db.isOpen(), db.isReadOnly(), db.inTransaction()));
db.setTransactionSuccessful();
} catch (Exception e) {
Log.e(TAG, "Error inserting batch record into database.", e);
} finally {
try {
db.endTransaction();
db.close();
} catch (Exception e) {
Log.e(TAG, Global.DB_ERROR, e);
}
}
}
Donc, je pense que peut-être une des deux choses se passe.
Cependant, je suis à court d'idées, mais je suis prêt à essayer tout ce qui est suggéré.
Je suis coincé avec plus ou moins le même problème et j'ai trouvé un défaut flagrant en la matière qui fait sens ...
https://code.google.com/p/Android/issues/detail?id=174566
Ma solution de contournement - bien que ce ne soit pas la meilleure solution - est de ne jamais modifier la révision de la base de données et de la suivre moi-même, donc de ne jamais appeler onUpgrade()
et d'effectuer manuellement une mise à niveau lors de la mise à jour de l'application.
Alternativement, si vous avez une petite base de données en lecture seule, vous pouvez déclencher la copie de la base de données dans chaque classe onCreate()
dans DBHelper
, mais cela pourrait donner des problèmes indésirables si le système de fichiers est plein. .
@Override
public void onCreate(SQLiteDatabase db) {
// Workaround for Issue 174566
myContext.deleteDatabase(DB_NAME);
try {
copyDataBase();
}
catch(IOException e) {
System.out.println("IOException " + e.getLocalizedMessage());
}
}
Mon application évolue désormais comme il se doit avec ma solution de contournement et en jugeant combien de temps s'est écoulé depuis que ce défaut a été signalé à l'origine, il se peut qu'il ne soit jamais corrigé du tout ...
Je suis désolé, ce n'est pas une solution complète au problème, mais c'est au moins une voie à suivre.
Ainsi, à première vue, la cause première de ce problème est celle d'une bibliothèque tierce. Sauf erreur, Tagit by Mobeix supprime la base de données au démarrage de l'application. J'ai ajouté une journalisation SQLite détaillée, y compris les règles suivantes:
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
J'ai remarqué dans le journal que ma base de données est en cours de dissociation après ma création et mon ouverture. Une journalisation plus détaillée indique que cela se produit lors de l'initialisation de la bibliothèque Mobeix. La ligne incriminée en question:
01-29 13:47:49.118: W/SQLiteLog(12120): (28) file unlinked while open: /data/user/0/com.company.app/databases/MyDatabase.db
Donc, mon fichier de base de données est non lié. Bizarre. Le prochain appel à getWritableDatabase () le recrée de nouveau, puis tout va bien jusqu'à ce que l'application soit supprimée et relancée, puis supprimée et recréée.
Je mettrai cela à jour si je découvre exactement ce qui cause le lien.
J'ai eu un problème similaire. Cependant, je supprimais délibérément la base de données actuelle dans le cadre d'une restauration.
Ce que je pense, c'est que SQLite marque la base de données en lecture seule afin d'offrir une protection contre le file unlinked while open:
.
Après la restauration, toutes les mises à jour échoueraient avec attempt to write a readonly database (code 1032)
.
Ma solution serait de rétablir le DBHelper. J'ai fait cela en ajoutant une méthode reopen
que j'appelle.
par exemple.
public static void reopen(Context context) {
instance = new DBHelper(context);
}
J'appelle/appelle ensuite cela en utilisant
if(copytaken && origdeleted && restoredone) {
DBHelper.reopen(context);
DBHelper.getHelper(context).expand(null,true);
}
L'appel à la méthode expand est mon équivalent/getaround pour onUpgrade/versions. Il ajoute des tables et des colonnes selon un pseudo-schéma comparé à la base de données réelle.
Le DBHelper complet est: -
/**
* DBHelper
*/
@SuppressWarnings("WeakerAccess")
class DBHelper extends SQLiteOpenHelper {
private static final String LOGTAG = "SW-DBHelper";
private static final String DBNAME = DBConstants.DATABASE_NAME;
private static final String dbcreated =
"001I Database " + DBNAME + " created.";
private static final String dbunusable =
"002E Database " + DBNAME +
" has been set as unusable (according to schema).";
private static final String dbexpanded =
"003I Database " + DBNAME + " expanded.";
private static final String dbexpandskipped =
"004I Database " + DBNAME + " expand skipped - nothing to alter.";
private static final String dbbuildskipped =
"005I Database" + DBNAME + " build skipped - no tables to add";
public static final String THISCLASS = DBHelper.class.getSimpleName();
/**
* Consrtuctor
*
* @param context activity context
* @param name database name
* @param factory cursorfactory
* @param version database version
*/
DBHelper(Context context, @SuppressWarnings("SameParameterValue") String name, @SuppressWarnings("SameParameterValue") SQLiteDatabase.CursorFactory factory, @SuppressWarnings("SameParameterValue") int version) {
super(context, name, factory, version);
}
/**
* Instantiates a new Db helper.
*
* @param context the context
*/
DBHelper(Context context) {
super(context, DBConstants.DATABASE_NAME, null, 1);
}
private static DBHelper instance;
/**
* Gets helper.
*
* @param context the context
* @return the helper
*/
static synchronized DBHelper getHelper(Context context) {
if(instance == null) {
instance = new DBHelper(context);
}
return instance;
}
@Override
public void onCreate(SQLiteDatabase db) {
expand(db, false);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldversion, int newversion) {
}
/**
* expand create database tables
*
* @param db SQLIte Database, if null then instance is used
* @param buildandexpand to attempt both create and expand
*/
void expand(SQLiteDatabase db, boolean buildandexpand) {
String mode = "Create Mode.";
if (buildandexpand) {
mode = "Expand Mode.";
}
String msg = mode;
String methodname = new Object(){}.getClass().getEnclosingMethod().getName();
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
// if no database has been passed then get the database
if(db == null) {
db = instance.getWritableDatabase();
}
// Build Tables to reflect schema (SHOPWISE) only if schema is usable
if(DBConstants.SHOPWISE.isDBDatabaseUsable()) {
// Check to see if any tables need to be added
ArrayList<String> buildsql = DBConstants.SHOPWISE.generateDBBuildSQL(db);
if (!buildsql.isEmpty()) {
DBConstants.SHOPWISE.actionDBBuildSQL(db);
msg = dbcreated + buildsql.size() + " tables added.";
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
} else {
msg = dbbuildskipped;
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
}
if(buildandexpand) {
ArrayList<String> altersql = DBConstants.SHOPWISE.generateDBAlterSQL(db);
if(!altersql.isEmpty()) {
msg = dbexpanded + altersql.size() + " columns added.";
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
DBConstants.SHOPWISE.actionDBAlterSQL(db);
} else {
msg = dbexpandskipped;
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
}
}
} else {
msg = dbunusable + "\n" +
DBConstants.SHOPWISE.getAllDBDatabaseProblemMsgs();
LogMsg.LogMsg(LogMsg.LOGTYPE_ERROR,LOGTAG,msg,THISCLASS,methodname);
}
}
public static void reopen(Context context) {
instance = new DBHelper(context);
}
}
J'ai eu le même problème. J'ai trouvé le moyen le plus simple est d'utiliser enableWriteAheadLogging () sur un objet de base de données.
databaseObject.enableWriteAheadLogging()
J'ai un problème similaire, ce qui est vraiment ennuyant, c'est que cela arrive à tout moment, il est difficile de reproduire les conditions exactes qui permettent que cela se produise. Je ferme seulement le DDBB dans la méthode ondestroy de la classe MainActivity. Ce que j'ai fait est d'ajouter un try/catch à chaque utilisation de la base de données et d'ajouter le catch suivant, dans ce cas, il se trouve au milieu d'une boucle while, dans d'autres fonctions, j'appelle de nouveau la fonction:
catch (SQLException e) {
e.printStackTrace();
Log.d(TAG, "MainService: error in AccSaveToDB with "+mainDB.getPath()+" in iteration "+j+". Closing and re-opening DB");
DBHelper.close();
mainDB.close();
j--;
}
Et ceci au début de chaque fonction qui accède à la base de données:
if (mainDB==null || !mainDB.isOpen()) {
DBHelper = DefSQLiteHelper.getInstance(getApplicationContext(), "Data.db", null, 1);
mainDB = DBHelper.getWritableDatabase();
}
Jusqu'ici, j'ai toujours cette erreur, je ne pouvais pas encore en comprendre la cause, mais au moins mon application ne plante pas et elle reprend ce qu'elle doit faire. Je ne vois pas si le fichier est en cours de suppression, mais cette solution fonctionne pour moi.