为了账号安全,请及时绑定邮箱和手机立即绑定

在 Oreo 上复制 SQLite 数据库

在 Oreo 上复制 SQLite 数据库

德玛西亚99 2022-12-07 15:27:34
在我的一个 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 中是否有任何更改导致此操作因某种原因失败?这可能是什么原因造成的?
查看完整描述

3 回答

?
aluckdog

TA贡献1847条经验 获得超7个赞

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 open 方法检查数据库是否存在,但要检查文件是否存在。

  3. 作为复制的一部分,删除 -wal 和 -shm 文件。

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

  • 没有不必要地打开(和创建)数据库的开销,只是检查它是否存在并创建目录。

  • 由于WAL优于 Journal 日志记录,该应用程序可能会得到改进。

以上全部包含在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;

}


查看完整回答
反对 回复 2022-12-07
?
慕少森

TA贡献2019条经验 获得超9个赞

在看到 logcat 之前我无法确定。但是,我认为您在访问数据库时可能缺少某些权限。


我想问一下检查您的AndroidManifest.xml文件中是否有以下提供程序以授予您的应用程序访问数据库的权限。这是一个包含提供商的示例清单文件。


<provider

    android:name=".DatabaseContentProvider"

    android:authorities="your.application.package.name"

    android:exported="false" />

你需要一个DatabaseContentProvider像下面这样的类。


import android.content.ContentProvider;

import android.content.ContentValues;

import android.database.Cursor;

import android.net.Uri;

import android.support.annotation.NonNull;

import android.support.annotation.Nullable;


public class DatabaseContentProvider extends ContentProvider {


    @Nullable

    @Override

    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {

        return null;

    }


    @Nullable

    @Override

    public Cursor query(@NonNull Uri uri,

                        @Nullable String[] projection,

                        @Nullable String selection,

                        @Nullable String[] selectionArgs,

                        @Nullable String sortOrder) {

        return null;

    }


    @Override

    public boolean onCreate() {

        return true;

    }


    @Override

    public int update(@NonNull Uri uri, @Nullable ContentValues values,

                      @Nullable String selection, @Nullable String[] selectionArgs) {

        return 0;

    }


    @Override

    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {

        return 0;

    }


    @Nullable

    @Override

    public String getType(@NonNull Uri uri) {

        return null;

    }

}

我在 Github 中有一个示例代码,用于 Android 中的基本数据库操作,您也可以在其中查看。


查看完整回答
反对 回复 2022-12-07
?
catspeake

TA贡献1111条经验 获得超0个赞

是的,对于 android 9,SQLite 数据库中有两个附加文件,如果您复制覆盖现有数据库的数据库,并且不先删除这两个文件,那么数据库将认为自己已损坏。请参阅此答案:Ship android app with pre populated database

但是@MikeT 应该得到赞扬,因为链接的答案是他开始的!!


查看完整回答
反对 回复 2022-12-07
  • 3 回答
  • 0 关注
  • 148 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号