Сбой приложения при возврате в резервную базу данных - PullRequest
0 голосов
/ 30 апреля 2019

Я создаю приложение для 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 ]

1 голос
/ 01 мая 2019

Код ошибки указывает, что вы не закрыли базу данных, а затем повторно приняли ее согласно

(1032) SQLITE_READONLY_DBMOVED

Код ошибки SQLITE_READONLY_DBMOVED - это расширенный код ошибки для SQLITE_READONLY. Код ошибки SQLITE_READONLY_DBMOVED указывает, что база данных не может быть изменен, потому что файл базы данных был перемещен с момента его открытия, и поэтому любая попытка изменить базу данных может привести к базе данных повреждение процессов, если происходит сбой, потому что журнал отката не быть правильно названным. Коды результатов и ошибок

То есть SQLite принимает решение, что его можно только читать, а не файл только для чтения.

Вы должны убедиться, что база данных закрыта или, что еще более важно, вы не пытаетесь получить доступ к базе данных через существующее открытие (с учетом того, что SQLiteOpenHelper использует то же открытие) согласно: -

После успешного открытия база данных кэшируется, поэтому вы можете вызывать этот метод каждый раз, когда вам нужно выполнить запись в базу данных. getWritableDatabase

Я бы также рекомендовал вместо того, чтобы удалять исходную базу данных в качестве первого шага, вместо того чтобы копировать или переименовывать ее, а затем удалять после успешного завершения копирования, это позволяет восстановить оригинал в случае возникновения проблем.

Я лично, чтобы обеспечить чистую ситуацию, перезапустить приложение после восстановления.

Ниже приведена основная часть кода восстановления, который у меня есть (что может оказаться полезным, перезапуск приложения в конце): -

/**************************************************************************
 * 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 будет или не будет записывать сообщения в журнал (включен только для разработки и может быть включен / выключен на разных уровнях вплоть до класса)
1 голос
/ 30 апреля 2019

Я думаю, что DATABASE_PATH, который вы используете, необходимо изменить. При восстановлении файла базы данных из резервной копии необходимо поместить файл в папку databases вместо папки files.

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

Пожалуйста, также проверьте, есть ли у вас разрешение поставщика и разрешение WRITE_EXTERNAL_STORAGE в вашем файле AndroidManifest.xml.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...