Android迁移SQLite项目遇SQLITE_READONLY_DBMOVED(1032)错误求因
我将Java项目迁移至Android平台并使用SQLite时,实例化MapleDBHelper类遇到如下异常:
attempt to write a readonly database (code 1032 SQLITE_READONLY_DBMOVED)
网上搜索未找到有效解决线索,请教可能的原因。
调用代码
try (MapleDBHelper mapledb = MapleDBHelper.getInstance(this.context); SQLiteDatabase db = mapledb.getWritableDatabase()) { // 修改数据库的代码 } catch (SQLiteException sqle) { Log.e("Failed to run all startup-bound database tasks", sqle.toString()); throw new IllegalStateException(sqle); }
MapleDBHelper类代码
public class MapleDBHelper extends SQLiteOpenHelper { public Context context; private static final String DATABASE_NAME = "cosmic"; private static final int DATABASE_VERSION = 1; private static MapleDBHelper sInstance; private MapleDBHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); this.context = context; } @Override public void onCreate(SQLiteDatabase db) { File databaseFile = context.getDatabasePath(DATABASE_NAME); if (databaseFile.exists()) { // 删除已有数据库文件 SQLiteDatabase.deleteDatabase(databaseFile); } try { AssetManager assetManager = context.getAssets(); InputStream db_database = assetManager.open("sql/1_db_database.sql"); InputStream db_drops = assetManager.open("sql/2_db_drops.sql"); InputStream db_shopupdate = assetManager.open("sql/3_db_shopupdate.sql"); InputStream db_admin = assetManager.open("sql/4_db-admin.sql"); db.execSQL(convertInputStreamToString(db_database)); db.execSQL(convertInputStreamToString(db_drops)); db.execSQL(convertInputStreamToString(db_shopupdate)); db.execSQL(convertInputStreamToString(db_admin)); db_database.close(); db_drops.close(); db_shopupdate.close(); db_admin.close(); } catch (FileNotFoundException e) { throw new RuntimeException("Could not read database file : " + e.getMessage()); } catch (IOException e) { throw new RuntimeException("Could not successfully parse database file " + e.getMessage()); } } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } public static synchronized MapleDBHelper getInstance(Context context) { if (sInstance == null) { sInstance = new MapleDBHelper(context.getApplicationContext()); } return sInstance; } }
堆栈信息
Stacktrace: 0 = {StackTraceElement@17117} "android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)" 1 = {StackTraceElement@17118} "android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:890)" 2 = {StackTraceElement@17119} "android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:756)" 3 = {StackTraceElement@17120} "android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:66)" 4 = {StackTraceElement@17121} "android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1920)" 5 = {StackTraceElement@17122} "android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1841)" 6 = {StackTraceElement@17123} "android.database.sqlite.SQLiteDatabase.setVersion(SQLiteDatabase.java:1102)" 7 = {StackTraceElement@17124} "android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:419)" 8 = {StackTraceElement@17125} "android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316)" 9 = {StackTraceElement@17126} "net.server.Server.init(Server.java:897)" 10 = {StackTraceElement@17127} "net.server.Server.main(Server.java:1017)" 11 = {StackTraceElement@17128} "com.mapleserver.MainActivity.startMapleServer(MainActivity.kt:34)" 12 = {StackTraceElement@17129} "com.mapleserver.MainActivity.onCreate(MainActivity.kt:28)"
核心问题
SQLITE_READONLY_DBMOVED异常的本质是:SQLite已打开某数据库文件,但该文件被移动或删除,导致后续写入操作失败。
你的onCreate方法存在致命错误:当onCreate被调用时,SQLiteOpenHelper已经打开了数据库连接(方法参数db就是已打开的实例),此时你直接删除了底层的数据库文件databaseFile。SQLite的连接仍指向原文件路径,但文件已不存在,后续执行execSQL或系统自动执行的setVersion操作时,就会触发只读异常。
解决步骤
移除
onCreate中删除数据库文件的代码onCreate被调用的前提是数据库文件原本不存在(或版本不匹配触发重建),此时根本不需要删除已有文件——如果文件存在,SQLiteOpenHelper不会调用onCreate,而是直接打开数据库。修改后的
onCreate方法:@Override public void onCreate(SQLiteDatabase db) { try { AssetManager assetManager = context.getAssets(); InputStream db_database = assetManager.open("sql/1_db_database.sql"); InputStream db_drops = assetManager.open("sql/2_db_drops.sql"); InputStream db_shopupdate = assetManager.open("sql/3_db_shopupdate.sql"); InputStream db_admin = assetManager.open("sql/4_db-admin.sql"); db.execSQL(convertInputStreamToString(db_database)); db.execSQL(convertInputStreamToString(db_drops)); db.execSQL(convertInputStreamToString(db_shopupdate)); db.execSQL(convertInputStreamToString(db_admin)); db_database.close(); db_drops.close(); db_shopupdate.close(); db_admin.close(); } catch (FileNotFoundException e) { throw new RuntimeException("无法读取数据库文件 : " + e.getMessage()); } catch (IOException e) { throw new RuntimeException("解析数据库文件失败 " + e.getMessage()); } }重置数据库需在外部处理
若确实需要重置数据库(如版本更新、用户手动重置),需在打开数据库之前删除文件,而非在onCreate内部操作:// 重置数据库的方法,需在调用getWritableDatabase前执行 public static void resetDatabase(Context context) { File dbFile = context.getDatabasePath(DATABASE_NAME); if (dbFile.exists()) { SQLiteDatabase.deleteDatabase(dbFile); // 重置单例,让下次getInstance创建新的Helper实例 sInstance = null; } }检查文件权限
确保应用对数据库目录有写入权限,Android 10+默认使用Scoped Storage,SQLiteOpenHelper的默认路径为应用私有目录,权限通常无需额外配置;若自定义路径,需确认对应目录的写入权限。
内容的提问来源于stack exchange,提问作者k0ala Xing

