简体   繁体   中英

App crash upon reverting to backup database

I'm making an Android app that downloads a database from a server to use locally on the phone. After a successful download, I make a copy of the .db file and save it in the data/data/app_name/files folder. In the event that the database becomes corrupted, I delete the original .db file and make a new one in it's place, writing everything from the backup file over to the new .db file. After this happens though, the next time that I try and use the database, I get an error saying that the file was deleted and that I'm attempting to write to a read-only database. What I would like to know is if this is a good method of backing up my database, and if so, what is going wrong with reverting to the backup.

Crash log

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)

My makeBackup method

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

    }

My revertToBackup method

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

I think the DATABASE_PATH that you are using needs to be changed. While restoring the database file from your backup, you need to put the file in the databases folder instead of your files folder.

/data/data/" + "your.application.package.goes.here" + "/databases/

Please also check if you have the provider authorization and have WRITE_EXTERNAL_STORAGE permission in your AndroidManifest.xml file.

The error code indicates that you have not closed the database and then re-accesed it as per

(1032) SQLITE_READONLY_DBMOVED

The SQLITE_READONLY_DBMOVED error code is an extended error code for SQLITE_READONLY. The SQLITE_READONLY_DBMOVED error code indicates that a database cannot be modified because the database file has been moved since it was opened, and so any attempt to modify the database might result in database corruption if the processes crashes because the rollback journal would not be correctly named. Result and Error Codes

That is SQLite is making the decision that it can only be read, rather than it being a read-only file.

You need to ensure that the database is closed or rather more importantly that you are not trying to access the database through an existing open (noting that SQLiteOpenHelper uses the same open) as per :-

Once opened successfully, the database is cached, so you can call this method every time you need to write to the database. getWritableDatabase

I would also recommend that rather than delete the original database as the first step that you instead copy or rename it and then delete it after the copy has been successfully done, this allows the original to be restored should there be issues.

I personally, to ensure a clean situation, restart the App after a restore.

The following is the core part of the restore code that I have (which you may find useful, restarting the App is at the end) :-

/**************************************************************************
 * 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 will or will not write messages to the log (only turned on for developing and can be turned on/off at varying levels down to a class)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM