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 对错误报告的评论是在几年后做出的,所以我不再有原始的崩溃代码来简单地证明概念。

您的问题可能略有不同,因为您没有复制数据库文件,但您似乎仍在修改它,因此根本原因可能相似。