SQLiteReadOnlyDatabaseException 在可写数据库上的数据库更新
SQLiteReadOnlyDatabaseException on database update on writeable database
每当我更新我的数据库时,我都会收到这个错误。但是当我按原样重新运行应用程序时,数据库会更新。
android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032)[
代码:
public DBAdapter(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
ctx = context;
db = getWritableDatabase();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
ctx.deleteDatabase(DATABASE_NAME);
new DBAdapter(ctx);
} else {
super.onUpgrade(db, oldVersion, newVersion);
}
}
作为建议的 SO 答案之一,我也添加了这个:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
顺便说一句:我正在使用 SQLiteAssetHelper 创建预建数据库
这不是防止此问题的解决方案,而是一种变通方法。
public DBAdapter(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
ctx = context;
try {
db = getWritableDatabase();
} catch (SQLiteReadOnlyDatabaseException e){
ctx.startActivity(new Intent(ctx, MainActivity.class));
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
ctx.deleteDatabase(DATABASE_NAME);
new DBAdapter(ctx);
} else {
super.onUpgrade(db, oldVersion, newVersion);
}
}
第一次初始化适配器时,会创建一个可写数据库。然后 onUpgrade 被调用。在这里,当数据库被删除时,适配器会重新初始化。但是数据库的连接没有被删除并且仍然存在,因此,第二次执行 db = getWritableDatabase();
时 SQLiteReadOnlyDatabaseException
发生。原来初始化DBAdapter的activity重启了。现在 Adapter 被重新初始化并且 onUpgrade
方法没有被调用,因此 SQLiteReadOnlyDatabaseException
不会发生。
所有这些过程发生得非常快,在我的情况下用户体验并没有变差。
注意:new DBAdapter(ctx);
似乎没有必要,deleteDatabase
似乎要重新创建适配器。但是为了谨慎起见,我写了这行代码。
我很想获得有关此错误的原因和解决方案的一些信息。
我在使用 Android SQLite 数据库时遇到了一些类似的问题。我很久以前在 https://code.google.com/p/android/issues/detail?id=174566 提交了一份关于它的错误报告。这份报告更详细地讨论了我对原因的调查结果。我不确定它是否与您的问题有关,但它似乎具有一些共同特征。
总结一下,我的调试表明Android打开数据库文件,调用onUpgrade(),如果在onUpgrade()调用期间替换数据库文件,Android侧文件handle 指向旧文件,因此当您 return 从 onUpgrade() 和 Android 尝试访问旧文件时导致应用程序崩溃。
这是我用来解决这个问题的一些代码:
应用程序启动时,我在 onCreate() 中执行了此操作:
Thread t = new Thread(new Runnable() {
@Override
public void run() {
Context context = getApplicationContext();
DBReader.copyDB(MainActivity.this);
DBReader.initialize(context);
}
});
t.start();
这会导致数据库文件的更新在应用程序启动时在后台发生,并且用户会对这个很棒的应用程序感到敬畏。因为我的文件比较大,复制需要一段时间。请注意,我在这里完全避免在 onUpgrade() 中做任何事情。
DBReader 是我自己的class,主要感兴趣的代码是这样的:
SharedPreferences prefs = context.getSharedPreferences(Const.KEY_PREFERENCES, Context.MODE_PRIVATE);
//here we have stored the latest version of DB copied
String dbVersion = prefs.getString(Const.KEY_DB_VERSION, "0");
int dbv = Integer.parseInt(dbVersion);
if (checkIfInitialized(context) && dbv == DBHelper.DB_VERSION) {
return;
}
File target = context.getDatabasePath(DBHelper.DB_NAME);
String path = target.getAbsolutePath();
//Log.d("Awesome APP", "Copying database to " + path);
path = path.substring(0, path.lastIndexOf("/"));
File targetDir = new File(path);
targetDir.mkdirs();
//Copy the database from assets
InputStream mInput = context.getAssets().open(DBHelper.DB_NAME);
OutputStream mOutput = new FileOutputStream(target.getAbsolutePath());
byte[] mBuffer = new byte[1024];
int mLength;
while ((mLength = mInput.read(mBuffer)) > 0) {
mOutput.write(mBuffer, 0, mLength);
}
mOutput.flush();
mOutput.close();
mInput.close();
SharedPreferences.Editor edit = prefs.edit();
edit.putString(Const.KEY_DB_VERSION, "" + DBHelper.DB_VERSION);
edit.apply();
和 checkIfInitialized() 的代码:
public static synchronized boolean checkIfInitialized(Context context) {
File dbFile = context.getDatabasePath(DBHelper.DB_NAME);
return dbFile.exists();
}
所以,简而言之,我完全避免了 onUpgrade() 并实现了我自己的自定义升级功能。这避免了 Android OS 在 onUpgrade().
中更改数据库导致旧的和无效的文件句柄崩溃的问题。
不过,我有点奇怪,如果您实际上最终升级数据库文件是为了让您升级数据库,那么 onUpgrade() 会导致 OS 使您的应用程序崩溃。 Google 对错误报告的评论是在几年后做出的,所以我不再有原始的崩溃代码来简单地证明概念。
您的问题可能略有不同,因为您没有复制数据库文件,但您似乎仍在修改它,因此根本原因可能相似。
每当我更新我的数据库时,我都会收到这个错误。但是当我按原样重新运行应用程序时,数据库会更新。
android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032)[
代码:
public DBAdapter(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
ctx = context;
db = getWritableDatabase();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
ctx.deleteDatabase(DATABASE_NAME);
new DBAdapter(ctx);
} else {
super.onUpgrade(db, oldVersion, newVersion);
}
}
作为建议的 SO 答案之一,我也添加了这个:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
顺便说一句:我正在使用 SQLiteAssetHelper 创建预建数据库
这不是防止此问题的解决方案,而是一种变通方法。
public DBAdapter(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
ctx = context;
try {
db = getWritableDatabase();
} catch (SQLiteReadOnlyDatabaseException e){
ctx.startActivity(new Intent(ctx, MainActivity.class));
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
ctx.deleteDatabase(DATABASE_NAME);
new DBAdapter(ctx);
} else {
super.onUpgrade(db, oldVersion, newVersion);
}
}
第一次初始化适配器时,会创建一个可写数据库。然后 onUpgrade 被调用。在这里,当数据库被删除时,适配器会重新初始化。但是数据库的连接没有被删除并且仍然存在,因此,第二次执行 db = getWritableDatabase();
时 SQLiteReadOnlyDatabaseException
发生。原来初始化DBAdapter的activity重启了。现在 Adapter 被重新初始化并且 onUpgrade
方法没有被调用,因此 SQLiteReadOnlyDatabaseException
不会发生。
所有这些过程发生得非常快,在我的情况下用户体验并没有变差。
注意:new DBAdapter(ctx);
似乎没有必要,deleteDatabase
似乎要重新创建适配器。但是为了谨慎起见,我写了这行代码。
我很想获得有关此错误的原因和解决方案的一些信息。
我在使用 Android SQLite 数据库时遇到了一些类似的问题。我很久以前在 https://code.google.com/p/android/issues/detail?id=174566 提交了一份关于它的错误报告。这份报告更详细地讨论了我对原因的调查结果。我不确定它是否与您的问题有关,但它似乎具有一些共同特征。
总结一下,我的调试表明Android打开数据库文件,调用onUpgrade(),如果在onUpgrade()调用期间替换数据库文件,Android侧文件handle 指向旧文件,因此当您 return 从 onUpgrade() 和 Android 尝试访问旧文件时导致应用程序崩溃。
这是我用来解决这个问题的一些代码:
应用程序启动时,我在 onCreate() 中执行了此操作:
Thread t = new Thread(new Runnable() {
@Override
public void run() {
Context context = getApplicationContext();
DBReader.copyDB(MainActivity.this);
DBReader.initialize(context);
}
});
t.start();
这会导致数据库文件的更新在应用程序启动时在后台发生,并且用户会对这个很棒的应用程序感到敬畏。因为我的文件比较大,复制需要一段时间。请注意,我在这里完全避免在 onUpgrade() 中做任何事情。
DBReader 是我自己的class,主要感兴趣的代码是这样的:
SharedPreferences prefs = context.getSharedPreferences(Const.KEY_PREFERENCES, Context.MODE_PRIVATE);
//here we have stored the latest version of DB copied
String dbVersion = prefs.getString(Const.KEY_DB_VERSION, "0");
int dbv = Integer.parseInt(dbVersion);
if (checkIfInitialized(context) && dbv == DBHelper.DB_VERSION) {
return;
}
File target = context.getDatabasePath(DBHelper.DB_NAME);
String path = target.getAbsolutePath();
//Log.d("Awesome APP", "Copying database to " + path);
path = path.substring(0, path.lastIndexOf("/"));
File targetDir = new File(path);
targetDir.mkdirs();
//Copy the database from assets
InputStream mInput = context.getAssets().open(DBHelper.DB_NAME);
OutputStream mOutput = new FileOutputStream(target.getAbsolutePath());
byte[] mBuffer = new byte[1024];
int mLength;
while ((mLength = mInput.read(mBuffer)) > 0) {
mOutput.write(mBuffer, 0, mLength);
}
mOutput.flush();
mOutput.close();
mInput.close();
SharedPreferences.Editor edit = prefs.edit();
edit.putString(Const.KEY_DB_VERSION, "" + DBHelper.DB_VERSION);
edit.apply();
和 checkIfInitialized() 的代码:
public static synchronized boolean checkIfInitialized(Context context) {
File dbFile = context.getDatabasePath(DBHelper.DB_NAME);
return dbFile.exists();
}
所以,简而言之,我完全避免了 onUpgrade() 并实现了我自己的自定义升级功能。这避免了 Android OS 在 onUpgrade().
中更改数据库导致旧的和无效的文件句柄崩溃的问题。不过,我有点奇怪,如果您实际上最终升级数据库文件是为了让您升级数据库,那么 onUpgrade() 会导致 OS 使您的应用程序崩溃。 Google 对错误报告的评论是在几年后做出的,所以我不再有原始的崩溃代码来简单地证明概念。
您的问题可能略有不同,因为您没有复制数据库文件,但您似乎仍在修改它,因此根本原因可能相似。