我正在制作一个Android应用程序,该程序可以从服务器下载数据库以在手机上本地使用。成功下载后,我将复制.db文件并将其保存在data / data / app_name / files文件夹中。万一数据库损坏了,我删除原始的.db文件并在其中放置一个新文件,将所有内容从备份文件写入新的.db文件。但是,在发生这种情况之后,下一次我尝试使用该数据库时,收到一条错误消息,指出该文件已删除,并且我尝试写入只读数据库。我想知道的是,这是否是备份数据库的好方法,如果是,那么还原到备份又出了什么问题。
崩溃日志
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.troubleshooting, PID: 10421
android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032)
#################################################################
Error Code : 1032 (SQLITE_READONLY_DBMOVED)
Caused By : Database or Journal file have been removed.
(attempt to write a readonly database (code 1032))
#################################################################
at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:904)
at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:2111)
at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:2039)
at Model.GuideDatabase.createTable(GuideDatabase.java:102)
at Model.GuideDatabase.writeToDatabase(GuideDatabase.java:238)
at Model.GuideDatabase.writeAllFiles(GuideDatabase.java:248)
at Controller.DownloadGuideTask.onPostExecute(DownloadGuideTask.java:153)
at Controller.DownloadGuideTask.onPostExecute(DownloadGuideTask.java:24)
at android.os.AsyncTask.finish(AsyncTask.java:695)
at android.os.AsyncTask.-wrap1(Unknown Source:0)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:712)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6944)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
我的makeBackup方法
public void makeBackup()
{
File file = new File(DATABASE_PATH);
String backupName = curContext.getFilesDir() + BACKUP_NAME;
try {
FileInputStream in = new FileInputStream(file);
OutputStream out = new FileOutputStream(backupName);
byte[] buffer = new byte[1024];
int length;
while((length = in.read(buffer)) > 0)
{
out.write(buffer, 0, length);
}
in.close();
out.flush();
out.close();
Log.i("Backup", "Backup made successfully at " + backupName);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
我的revertToBackup方法
public boolean revertToBackup()
{
boolean success = false;
//setup
File file = new File(DATABASE_PATH);
String backupName = curContext.getFilesDir() + BACKUP_NAME;
try {
//get rid of the old database file, it may be corrupted or something like that
if(file.exists())
file.delete();
file = new File(backupName);
if(file.exists()) {
file.setReadable(true);
file.setWritable(true);
//take in from the backup file, put into the new file
FileInputStream in = new FileInputStream(backupName);
OutputStream out = new FileOutputStream(DATABASE_PATH);
byte[] buffer = new byte[1024];
int length;
//write the whole file to the new file
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
//cleanup
in.close();
out.flush();
out.close();
Log.i("Backup", "Backup successfully restored");
success = true;
}
else {
Log.i("Backup", "Backup not found");
Toast t = Toast.makeText(curContext, "Backup not found, must re-download database to continue", Toast.LENGTH_LONG);
t.show();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return success;
}
答案 0 :(得分:1)
我认为您正在使用的DATABASE_PATH
需要更改。从备份还原数据库文件时,需要将文件放在databases
文件夹中,而不是files
文件夹中。
/data/data/" + "your.application.package.goes.here" + "/databases/
还请检查您的WRITE_EXTERNAL_STORAGE
文件中是否具有提供者授权和AndroidManifest.xml
权限。
答案 1 :(得分:1)
错误代码表明您尚未关闭数据库,然后根据要求重新访问数据库。
(1032)SQLITE_READONLY_DBMOVED
SQLITE_READONLY_DBMOVED错误代码是SQLITE_READONLY的扩展错误代码。的 SQLITE_READONLY_DBMOVED错误代码指示数据库不能 已修改,因为数据库文件自打开以来已被移动, 因此,任何修改数据库的尝试都可能导致数据库 如果进程崩溃,则该损坏,因为回滚日志将 没有正确命名。 Result and Error Codes
SQLite决定只能读取它,而不是将其作为只读文件。
您需要确保数据库是关闭的,或更重要的是,您不要按照以下规定通过现有的打开项(请注意SQLiteOpenHelper使用相同的打开项)来访问数据库:
成功打开后,数据库将被缓存,因此您每次需要写入数据库时都可以调用此方法。 getWritableDatabase
我还建议您不要删除原始数据库,而是先复制或重命名它,然后在成功完成复制后再删除它,这是允许您在出现问题时还原原始数据库的第一步。 / p>
我个人为确保环境整洁,请在还原后重新启动应用程序。
以下是我拥有的还原代码的核心部分(您可能会发现这很有用,在最后重启应用程序):-
/**************************************************************************
* method dorestore - Restore Database in 3 stages
* 1) make a copy of the databasefile
* 2) delete the database
* 3) create the database populating by copying from the designated backup
* If an IOexception occurs and the database has been deleted revert to the
* copy
*/
private void doDBRestore() {
final String methodname = new Object(){}.getClass().getEnclosingMethod().getName();
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,"Invoked",this,methodname);
confirmaction = true;
logtag = "DB RESTORE";
//ArrayList<String> errorlist = new ArrayList<>();
resulttitle = "Restore Failed.";
errlist.clear();
dbfile = new File(currentdbfilename);
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,"In Progress set and dispalyed",this,methodname);
busy.show();
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,"New Thread Started",this,methodname);
new Thread(new Runnable() {
@Override
public void run() {
try {
// Stage 1 Create a copy of the database
String msg = "Stage 1 (make Copy of current DB)Starting";
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
FileInputStream fis = new FileInputStream(dbfile);
OutputStream backup = new FileOutputStream(copydbfilename);
while ((copylength = fis.read(buffer)) > 0) {
backup.write(buffer, 0, copylength);
}
backup.flush();
backup.close();
fis.close();
bkpfile = new File(copydbfilename);
msg = "Stage 1 - Complete. Copy made of current DB.";
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
copytaken = true;
// Stage 2 - Delete the database file
if (dbfile.delete()) {
msg = "Stage 2 - Completed. Original DB deleted.";
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
origdeleted = true;
// Added for Android 9+ to delete shm and wal file if they exist
File dbshm = new File(dbfile.getPath() + "-shm");
File dbwal = new File(dbfile.getPath()+ "-wal");
if (dbshm.exists()) {
dbshm.delete();
}
if (dbwal.exists()) {
dbwal.delete();
}
}
// Stage 3 copy from the backup to the deleted database file i.e. create it
msg = "Stage 3 - (Create new DB from backup) Starting.";
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
FileInputStream bkp = new FileInputStream(backupfilename);
OutputStream restore = new FileOutputStream(currentdbfilename);
copylength = 0;
while ((copylength = bkp.read(buffer)) > 0) {
restore.write(buffer, 0, copylength);
}
msg = "Stage 3 - Data Written";
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
restore.flush();
restore.close();
msg = "Stage 3 - New DB file flushed and closed";
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
restoredone = true;
bkp.close();
msg = "Stage 3 - Complete.";
LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
} catch (IOException e) {
e.printStackTrace();
if(!copytaken) {
errlist.add("Restore failed copying current database. Error was " + e.getMessage());
} else {
if(!origdeleted) {
errlist.add("Restore failed to delete current database. Error was " + e.getMessage());
}
else {
if(!restoredone) {
errlist.add("Restore failed to recreate the database from the backup. Error was "+ e.getMessage());
errlist.add("Restore will attempt to revert to the original database.");
}
}
}
}
// Ouch restore not done but DB deleted so recover from
// copy by renaming copy
if (copytaken && origdeleted && !restoredone) {
String msg = "Restore failed. Recovering DB after failed restore from backup";
LogMsg.LogMsg(LogMsg.LOGTYPE_ERROR,LOGTAG,msg,THISCLASS,methodname);
//File rcvdbfile = new File(copydbfilename);
//noinspection ResultOfMethodCallIgnored
bkpfile.renameTo(dbfile);
msg = "Restore failed. DB Recovered from backup now in original state.";
LogMsg.LogMsg(LogMsg.LOGTYPE_ERROR,LOGTAG,msg,THISCLASS,methodname);
rolledback = true;
errlist.add("Database reverted to original.");
}
if (copytaken && !origdeleted) {
//noinspection ResultOfMethodCallIgnored
bkpfile.delete();
String msg = "Restore failed. Original DB not deleted so original\" +\n" +
" \" is being used.";
LogMsg.LogMsg(LogMsg.LOGTYPE_ERROR,LOGTAG,msg,THISCLASS,methodname);
}
if(!copytaken) {
String msg = "Restore failed. Attempt to Copy original DB failed.\" +\n" +
" \" Original DB is being used.";
LogMsg.LogMsg(LogMsg.LOGTYPE_ERROR,LOGTAG,msg,THISCLASS,methodname);
}
if(copytaken && origdeleted && restoredone) {
//noinspection ResultOfMethodCallIgnored
bkpfile.delete();
errlist.add("Database successfully restored.");
resulttitle = "Restore was successful. Application wil be restarted.";
DBHelper.reopen(context);
DBHelper.getHelper(context).expand(null,true);
}
StringBuilder fm = new StringBuilder(finalmessage);
for(int i = 0; i < errlist.size(); i++){
if(i > 0) {
fm.append("\n\n");
}
fm.append(errlist.get(i));
}
finalmessage = fm.toString();
runOnUiThread(new Runnable() {
@Override
public void run() {
busy.dismiss();
AlertDialog.Builder resultdialog = new AlertDialog.Builder(context);
resultdialog.setTitle(resulttitle);
resultdialog.setMessage(finalmessage);
resultdialog.setCancelable(true);
resultdialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (copytaken && origdeleted && restoredone) {
Intent i = getBaseContext().getPackageManager()
.getLaunchIntentForPackage( getBaseContext().getPackageName() );
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
finish();
startActivity(i);
System.exit(0);
}
}
});
resultdialog.show();
}
});
}
}).start();
}