从 SD 卡恢复 Room Sqlite 数据库不工作

Restore from Room Sqlite database from sd card not working

我查看了 Backup and restore SQLite database to sdcard and Restoring SQLite DB file 等其他答案,但当我卸载并重新安装应用程序并恢复备份时,我仍然没有看到数据库正在恢复。 这是我目前的代码。

        public class BackupAndRestore {
        
            public static void importDB(Context context) {
                try {
                    File sd = Environment.getExternalStorageDirectory();
        
                    if (sd.canRead()) {
                        File currentDB = context.getDatabasePath(AppDatabase.DATABASE_NAME);
                        File backupDB = new File(sd, AppDatabase.DATABASE_NAME);
        
                        if (currentDB.exists()) {
                            FileChannel src = new FileInputStream(backupDB).getChannel();
                            FileChannel dst = new FileOutputStream(currentDB).getChannel();
                            dst.transferFrom(src, 0, src.size());
                            src.close();
                            dst.close();
                            Toast.makeText(context, "Database Restored successfully", Toast.LENGTH_SHORT).show();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        
            public static void exportDB(Context context) {
                try {
                    File sd = Environment.getExternalStorageDirectory();
                    File data = Environment.getDataDirectory();
        
        
                    if (sd.canWrite()) {
        
                        File currentDB = context.getDatabasePath(AppDatabase.DATABASE_NAME);
                        File backupDB = new File(sd, AppDatabase.DATABASE_NAME);
        
                        if (currentDB.exists()) {
                            FileChannel src = new FileInputStream(currentDB).getChannel();
                            FileChannel dst = new FileOutputStream(backupDB).getChannel();
                            dst.transferFrom(src, 0, src.size());
                            src.close();
                            dst.close();
                            Toast.makeText(context, "Backup is successful to SD card", Toast.LENGTH_SHORT).show();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
        
            }
        
        }

所以我安装应用程序并将内容添加到数据库。然后我在调用上面的这个导出方法之前也授予了写入外部存储的权限。它显示 toast 消息“备份成功...”,我可以看到在外部存储中创建的文件。然后我卸载并重新安装,并再次请求对外部存储的许可。然后调用上面的方法import。再次看到 toast 消息“数据库已恢复..”,但我看不到之前存在的数据库内容。我在 android 7 设备和 android 10 设备上进行了测试。 我会感谢帮助。谢谢

您的问题很可能是数据库使用的是 WAL (Write-Ahead logging) 而不是日志模式。

对于 WAL,更改将写入 WAL 文件(数据库文件名后缀为 -wal 以及另一个文件 -shm)。如果数据库还没有被提交,而你只有 backup/restore 数据库文件。您将丢失数据。

  • 完全提交后,-wal 文件将为 0 字节或不存在,在这种情况下不需要它。

从 Android9 开始,默认从日志模式更改为 WAL。

假设这是您的问题,您有一些选择:-

  1. 使用日志模式(例如使用 SQLiteDatabase disableWriteAheadLogging 方法)
  2. Backup/Restore 所有 3 个文件(如果存在)
  3. 完全提交数据库然后备份(关闭数据库应该完全提交)和delete/rename恢复前的-wal文件和-shm文件。

选项 3 将是推荐的方式,因为您将获得 WAL 的优势。

这里有一个完全检查点的例子(有点过头但它有效):-

private void checkpointIfWALEnabled(Context context) {
    final String TAG = "WALCHKPNT";
    Cursor csr;
    int wal_busy = -99, wal_log = -99, wal_checkpointed = -99;
    SQLiteDatabase db = SQLiteDatabase.openDatabase(context.getDatabasePath(DBConstants.DATABASE_NAME).getPath(), null, SQLiteDatabase.OPEN_READWRITE);
    csr = db.rawQuery("PRAGMA journal_mode", null);
    if (csr.moveToFirst()) {
        String mode = csr.getString(0);
        //Log.d(TAG, "Mode is " + mode);
        if (mode.toLowerCase().equals("wal")) {
            csr = db.rawQuery("PRAGMA wal_checkpoint", null);
            if (csr.moveToFirst()) {
                wal_busy = csr.getInt(0);
                wal_log = csr.getInt(1);
                wal_checkpointed = csr.getInt(2);
            }
            //Log.d(TAG,"Checkpoint pre checkpointing Busy = " + String.valueOf(wal_busy) + " LOG = " + String.valueOf(wal_log) + " CHECKPOINTED = " + String.valueOf(wal_checkpointed) );
            csr = db.rawQuery("PRAGMA wal_checkpoint(TRUNCATE)", null);
            csr.getCount();
            csr = db.rawQuery("PRAGMA wal_checkpoint", null);
            if (csr.moveToFirst()) {
                wal_busy = csr.getInt(0);
                wal_log = csr.getInt(1);
                wal_checkpointed = csr.getInt(2);
            }
            //Log.d(TAG,"Checkpoint post checkpointing Busy = " + String.valueOf(wal_busy) + " LOG = " + String.valueOf(wal_log) + " CHECKPOINTED = " + String.valueOf(wal_checkpointed) );
        }
    }
    csr.close();
    db.close(); // Should checkpoint the database anyway.
}

恢复时使用以下删除-wal和-shm文件:-

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

经过多次测试和环顾四周,感谢@MikeT,我能够使用下面的 class 进行备份和恢复。

public class BackupAndRestore {

        public static void importDB(Context context) {
            try {
                File sd = Environment.getExternalStorageDirectory();
                // by closing the database some other database files ending with -shm and -wal are deleted so that there is one database file and it has all content
                AppDatabase.getDatabaseInstance(context).close();

                if (sd.canRead()) {
                    File currentDB = context.getDatabasePath(AppDatabase.DATABASE_NAME);
                    File backupDB = new File(sd, AppDatabase.DATABASE_NAME);

                    transfer(context, backupDB, currentDB);
                    Toast.makeText(context, "Database Restored successfully", Toast.LENGTH_SHORT).show();

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        public static void exportDB(Context context) {
            try {
                File sd = Environment.getExternalStorageDirectory();
                // by closing the database some other database files ending with -shm and -wal are deleted so that there is one database file and it has all content
                AppDatabase.getDatabaseInstance(context).close();

                if (sd.canWrite()) {
                    File currentDB = context.getDatabasePath(AppDatabase.DATABASE_NAME);
                    File backupDB = new File(sd, AppDatabase.DATABASE_NAME);
                    transfer(context, currentDB, backupDB);

                    Toast.makeText(context, "Backup is successful to SD card", Toast.LENGTH_SHORT).show();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private static void transfer(Context context, File sourceFile, File destinationFile) throws IOException {
            if (sourceFile.exists()) {
                FileChannel src = new FileInputStream(sourceFile).getChannel();
                FileChannel dst = new FileOutputStream(destinationFile).getChannel();
                dst.transferFrom(src, 0, src.size());
                src.close();
                dst.close();
            }
        }

    }

在我的主要 activity 中,我调用如下方法

    ...

    if (item.getItemId() == R.id.backup) {
        BackupAndRestore.exportDB(getBaseContext());
        restartApplication();
    } else if (item.getItemId() == R.id.restore) {
        BackupAndRestore.importDB(getBaseContext());
        restartApplication();
    }

    ...

所以在备份后调用了 restartApplication 方法,因为我注意到房间数据库在备份后无法正常工作,除非我重新启动应用程序,而且在恢复后,除非重新启动应用程序,否则我看不到恢复的数据。重启方法如下

private void restartApplication() {
    finish();
    startActivity(getIntent());
    System.exit(0);
}