Java 应用程序在恢复到备份数据库时崩溃

Java 应用程序在恢复到备份数据库时崩溃,java,android,sqlite,Java,Android,Sqlite,我正在制作一个Android应用程序,从服务器下载一个数据库,在手机上本地使用。成功下载后,我复制了一份.db文件,并将其保存在data/data/app_name/files文件夹中。如果数据库损坏,我会删除原始的.db文件,并在原处创建一个新文件,将备份文件中的所有内容写入新的.db文件。但是,在这种情况发生之后,下次尝试使用数据库时,我会收到一个错误,表示文件已被删除,并且我正在尝试写入只读数据库。我想知道的是,这是否是备份数据库的一种好方法,如果是,恢复到备份会出现什么问题 事故日志 E

我正在制作一个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();
        }

    }
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;
    }
我的备份方法

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();
        }

    }
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;
    }
我认为您正在使用的数据库路径需要更改。从备份还原数据库文件时,需要将该文件放在databases文件夹而不是files文件夹中


还请检查您是否具有提供程序授权,以及在AndroidManifest.xml文件中是否具有写入外部存储权限

错误代码表示您尚未关闭数据库,然后根据重新访问它

1032 SQLITE_只读_DBMOVED SQLITE_READONLY_DBMOVED错误代码是SQLITE_READONLY的扩展错误代码。这个 SQLITE_READONLY_DBMOVED错误代码表示无法删除数据库 已修改,因为数据库文件在打开后已被移动, 因此,任何修改数据库的尝试都可能导致数据库错误 如果进程因回滚日志丢失而崩溃,则会损坏 名称不正确

也就是说,SQLite决定它只能读取,而不是只读文件

您需要确保数据库已关闭,或者更重要的是,您没有试图通过现有的open访问数据库,注意SQLiteOpenHelper使用相同的open,如下所示:-

一旦成功打开,数据库将被缓存,因此您可以在每次需要写入数据库时调用此方法

我还建议您不要将删除原始数据库作为第一步,而是复制或重命名它,然后在复制成功后将其删除,这样可以在出现问题时恢复原始数据库

我个人认为,为了确保情况干净,请在恢复后重新启动应用程序

以下是我所拥有的还原代码的核心部分,您可能会发现这些代码很有用,最后将重新启动应用程序:-

/**************************************************************************
 * 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();
}
LogMsg将或不将消息写入日志,仅在开发时打开,并且可以在不同级别上打开/关闭,直至类
请张贴日志。