java - 在 Oreo 上复制 SQLite 数据库

标签 java android database sqlite

在我的一个 Android 应用程序中,我使用了一个预填充的数据库,我在第一次使用时复制了该数据库。到目前为止,这在所有 Android 版本上运行良好,但对于 API 28,它会失败。它确实复制了数据库,之后它可以连接到数据库,但由于某种原因,无法访问任何表。我得到该表不存在的错误。

我在模拟器上下载了数据库,检查了一下,数据库没有问题。我在代码中创建数据库的另一个应用程序运行良好(创建数据库后可以找到表)。

我用来复制数据库的代码:

public void copyDatabase() throws IOException{
    InputStream myInput = mContext.getAssets().open(mDbSource);

    // Path to the just created empty db
    String outFileName = getDbPath() + mDbName;

    //Open the empty db as the output stream
    OutputStream myOutput = new FileOutputStream(outFileName);

    //transfer bytes from the inputfile to the outputfile
    byte[] buffer = new byte[1024];
    int length;
    while ((length = myInput.read(buffer))>0){
        myOutput.write(buffer, 0, length);
    }

    //Close the streams
    myOutput.flush();
    myOutput.close();
    myInput.close();
}

private String getDbPath(){
    return "/data/data/"+mContext.getResources().getString(R.string.package_name)+"/databases/";
}

API 28 中是否有任何更改导致此操作因某种原因失败?这可能是什么原因造成的?

最佳答案

API 28 开启的问题是 WAL(预写日志记录)在默认情况下处于开启状态。通常情况下,如果打开数据库(通常在检查数据库是否存在时完成),则会创建两个文件 -wal 和 -shm 文件。

例如在您的代码中,这似乎表明这就是您正在做的事情:-

// Path to the just created empty db
String outFileName = getDbPath() + mDbName;

当现有数据库被副本覆盖时,-wal 和 -shm 文件仍然存在。

当最终尝试将数据库作为 SQLiteDatabase 打开时,会引发数据库与 -wal 和 -shm 文件之间的不匹配。底层的 SQLiteDatabase open 然后决定数据库无法打开,因此,为了提供可用的数据库,删除并重新创建数据库有效地清空数据库,因此出现这种情况。

三种解决方法

  1. 覆盖 onConfigure 方法并强制它使用旧的日志模式(使用 [disableWriteAheadLogging](https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteDatabase#disablewriteaheadlogging 方法))。
  2. 使用 SQLiteDatabase 打开方法检查数据库是否存在,但要检查文件是否存在。
  3. 作为复制的一部分,删除 -wal 和 -shm 文件。

我建议 2 或 3 是更好的方法:-

  • 没有不必要的打开(和创建)数据库的开销,只是检查它是否存在并创建目录。
  • 由于 advantages of a WAL,该应用程序可能会得到改进通过日志记录。

以上内容均在Ship android app with pre populated database中涵盖.

解决方案代码实际上结合了 2 和 3(尽管应该不需要修复 3(因为修复 2 不会将数据库作为 SQLiteDatabase 打开))。

说我相信以下用于检查数据库是否存在的代码(修复 2)比上面答案中的等效代码更好:-

/**
 * Check if the database already exists. NOTE will create the databases folder is it doesn't exist
 * @return true if it exists, false if it doesn't
 */
public static boolean checkDataBase(Context context, String dbname) {

    File db = new File(context.getDatabasePath(dbname).getPath()); //Get the file name of the database
    Log.d("DBPATH","DB Path is " + db.getPath()); //TODO remove if publish App
    if (db.exists()) return true; // If it exists then return doing nothing

    // Get the parent (directory in which the database file would be)
    File dbdir = db.getParentFile();
    // If the directory does not exits then make the directory (and higher level directories)
    if (!dbdir.exists()) {
        db.getParentFile().mkdirs();
        dbdir.mkdirs();
    }
    return false;
}

关于java - 在 Oreo 上复制 SQLite 数据库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56241576/

相关文章:

Android:DialogFragment 中的位图大小超过 32 位

mysql - 根据品牌ID获取品牌名称

database - 需要一些帮助(搜索算法)

java - Hystrix 和连接池

java - 在java中使用数组有问题吗?

android - 自定义 Lint 检查测试文件

java - 如何将 Activity 引用传递给我自己的组件

android - 什么是现有 SQLite 数据库的完整 Android 数据库助手类?

java - 使用 Hibernate 的 Java Web 服务中的内存泄漏

java - JGit 中的文件提交日期