数据库导入和导出在Android Pie中不起作用

时间:2019-01-05 11:01:07

标签: java android sqlite android-9.0-pie

下面是导入和导出SQLite数据库的工作方法。在除Android Pie外的所有android版本中,其工作情况都很好。当我尝试导入Android Pie时,它显示成功的Toast,但未还原数据库。谁能帮我解决Android Pie(API 28)问题。

private void importDB() {

    try {
        File sd = Environment.getExternalStorageDirectory();
        File cur_db_pat = new File(this.getDatabasePath(DATABASE_NAME).getAbsolutePath());

        if (sd.canWrite()) {
            String backupDBPath = bac_dir_nam +"/" + DATABASE_NAME;
            File currentDB = new File(sd, backupDBPath);

            FileChannel src = new FileInputStream(currentDB).getChannel();
            FileChannel dst = new FileOutputStream(cur_db_pat).getChannel();
            dst.transferFrom(src, 0, src.size());
            src.close();
            dst.close();
            Toast.makeText(getBaseContext(), cur_db_pat.toString(),
                    Toast.LENGTH_LONG).show();
        }
    } catch (Exception e) {

        Toast.makeText(getBaseContext(), e.toString(), Toast.LENGTH_LONG)
                .show();

    }
}

private void exportDB() {

    try {
        File sd = Environment.getExternalStorageDirectory();
        File cur_db_pat = new File(this.getDatabasePath(DATABASE_NAME).getAbsolutePath());

        if (sd.canWrite()) {
            String backupDBPath = bac_dir_nam+"/" + DATABASE_NAME;
            File backupDB = new File(sd, backupDBPath);

            FileChannel src = new FileInputStream(cur_db_pat).getChannel();
            FileChannel dst = new FileOutputStream(backupDB).getChannel();
            dst.transferFrom(src, 0, src.size());
            src.close();
            dst.close();
            Toast.makeText(getBaseContext(), backupDB.toString(),
                    Toast.LENGTH_LONG).show();

        }
    } catch (Exception e) {

        Toast.makeText(getBaseContext(), e.toString(), Toast.LENGTH_LONG)
                .show();

    }
}

我对文件系统没有太多经验。因此,举个例子会很有帮助。

3 个答案:

答案 0 :(得分:6)

在Android Pie +中,SQLite已更改为默认设置,以使用通常更有效的预写日志记录( WAL )代替日记模式。

因此,将有两个文件与数据库同名,但后缀为 -shm (共享内存文件)和 -wal (预写日志),并且我认为它们的存在是造成问题的原因。 Temporary Files Used By SQLite (see 2.2 and 2.3)

一种解决方法是使用SQliteDatabase disableWriteAheadLogging 方法禁用预写日志记录,而先前的方法将像以前一样工作,但日志模式效率较低。

  • (如果使用 SQliteOpenHelper 的子类,然后覆盖 onConfigure 方法以调用此方法。)disableWriteAheadLogging

另一个解决方法是在还原时删除这两个文件。为了避免潜在的损坏,必须在进行备份之前确保对数据库进行适当的检查。参见PRAGMA checkpoint;

以下是还原时删除这两个文件的摘要(请注意,假定已使用足够的检查点进行了备份):-

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

另一种解决方法是另外备份并随后还原-shm和-wal文件。

您可能还希望考虑在导入/还原时重命名原始文件的潜在好处,如果结果表明没有问题,则删除后,在复制新文件(例如,使用PRAGMA integrity_check;之后检查它们)重命名的原始文件,否则删除导入的文件,然后将原始文件重命名为其原始名称,表明导入失败。

答案 1 :(得分:2)

在您的Db WorkHelper类中,使用ovverride onOpen()方法并设置 disableWriteAheadLogging ,然后调用onOpen()标准(如果版本为android sdk 28,请确保旧版本仍然是旧模式)。

@Override
public void onOpen(SQLiteDatabase database) {
    super.onOpen(database);
    if(Build.VERSION.SDK_INT >= 28)
    {
        database.disableWriteAheadLogging();
    }
}

在我看来,这很完美。

答案 2 :(得分:0)

与其他评论者所建议的不同,在禁用预写日志记录后,您不能依赖由单个文件组成的数据库,并且您不能假设 -shl 和 -wal 文件名保持正确。这是sqlite3 / Android的所有实现细节,因此随时可能更改(就像旧代码损坏一样)。

我希望继续工作的一种方法是使用 sqlite3 的 .dump 命令将数据库转换为以后可以执行以重新创建数据库的 SQL。

我还没有测试以下内容,但希望类似的东西应该可以工作:

// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0

// Untested:
    private byte[] exportDatabase(SQLiteDatabase database) throws IOException {
        Process process = new ProcessBuilder()
                .command("/system/bin/sqlite3", database.getPath(), ".dump")
                .redirectOutput(ProcessBuilder.Redirect.PIPE)
                .start();
        try (InputStream inputStream = process.getInputStream()) {
            // [read the full contents of inputStream and save them somewhere]
            return ByteStreams.toByteArray(inputStream);
        } finally {
            waitForProcess(process);
        }
    }

    private void importDatabase(String databasePath, InputStream backedUpData) throws IOException {
        // restore the database:
        Process process = new ProcessBuilder()
                .command("/system/bin/sqlite3", databasePath)
                .redirectInput(ProcessBuilder.Redirect.PIPE)
                .start();
        try (OutputStream outputStream = process.getOutputStream()) {
            // now write the backed-up contents back to outputStream
            ByteStreams.copy(backedUpData, outputStream);
        } 
        waitForProcess(process);
    }

    private static void waitForProcess(Process process) {
        try {
            process.waitFor();
        } catch (InterruptedException e) {
            // ignore interruption, restore interrupt flag
            Thread.currentThread().interrupt();
        }
    }

显然,您必须确保:

  • 您要备份的数据库当前未打开。
  • 您要恢复的数据库不存在。