恢复到备份数据库时应用崩溃

时间:2019-04-30 17:48:39

标签: java android sqlite

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

2 个答案:

答案 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();
}
  • LogMsg会或不会将消息写入日志(仅打开以进行开发,并且可以在不同级别(直至一个类)打开/关闭)