在 Oreo 上复制 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 中是否有任何更改导致此操作因某种原因失败?这可能是什么原因造成的?


德玛西亚99
浏览 172回答 3
3回答

aluckdog

API 28 开启的问题是默认情况下 WAL(预写日志记录)是开启的。通常情况下,如果打开数据库(通常在检查数据库是否存在时完成),则会创建两个文件 -wal 和 -shm 文件。例如,在您的代码中,这似乎表明这就是您正在做的事情:-// Path to the just created empty db String outFileName = getDbPath() + mDbName;当现在存在的数据库被副本覆盖时,-wal 和 -shm 文件仍然存在。当最终尝试将数据库作为 SQLiteDatabase 打开时,会引发数据库与 -wal 和 -shm 文件之间的不匹配。底层的 SQLiteDatabase open 然后决定无法打开数据库,因此,为了提供可用的数据库,删除并重新创建数据库有效地清空数据库,因此出现了这种情况。有三种方法可以解决这个问题覆盖onConfigure方法并强制它使用旧的日志模式(使用 [disableWriteAheadLogging](https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteDatabase#disablewriteaheadlogging方法))。使用 SQLiteDatabase open 方法检查数据库是否存在,但要检查文件是否存在。作为复制的一部分,删除 -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;}

慕少森

在看到 logcat 之前我无法确定。但是,我认为您在访问数据库时可能缺少某些权限。我想问一下检查您的AndroidManifest.xml文件中是否有以下提供程序以授予您的应用程序访问数据库的权限。这是一个包含提供商的示例清单文件。<provider&nbsp; &nbsp; android:name=".DatabaseContentProvider"&nbsp; &nbsp; android:authorities="your.application.package.name"&nbsp; &nbsp; 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 {&nbsp; &nbsp; @Nullable&nbsp; &nbsp; @Override&nbsp; &nbsp; public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {&nbsp; &nbsp; &nbsp; &nbsp; return null;&nbsp; &nbsp; }&nbsp; &nbsp; @Nullable&nbsp; &nbsp; @Override&nbsp; &nbsp; public Cursor query(@NonNull Uri uri,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Nullable String[] projection,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Nullable String selection,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Nullable String[] selectionArgs,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Nullable String sortOrder) {&nbsp; &nbsp; &nbsp; &nbsp; return null;&nbsp; &nbsp; }&nbsp; &nbsp; @Override&nbsp; &nbsp; public boolean onCreate() {&nbsp; &nbsp; &nbsp; &nbsp; return true;&nbsp; &nbsp; }&nbsp; &nbsp; @Override&nbsp; &nbsp; public int update(@NonNull Uri uri, @Nullable ContentValues values,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @Nullable String selection, @Nullable String[] selectionArgs) {&nbsp; &nbsp; &nbsp; &nbsp; return 0;&nbsp; &nbsp; }&nbsp; &nbsp; @Override&nbsp; &nbsp; public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {&nbsp; &nbsp; &nbsp; &nbsp; return 0;&nbsp; &nbsp; }&nbsp; &nbsp; @Nullable&nbsp; &nbsp; @Override&nbsp; &nbsp; public String getType(@NonNull Uri uri) {&nbsp; &nbsp; &nbsp; &nbsp; return null;&nbsp; &nbsp; }}我在 Github 中有一个示例代码,用于 Android 中的基本数据库操作,您也可以在其中查看。

catspeake

是的,对于 android 9,SQLite 数据库中有两个附加文件,如果您复制覆盖现有数据库的数据库,并且不先删除这两个文件,那么数据库将认为自己已损坏。请参阅此答案:Ship android app with pre populated database但是@MikeT 应该得到赞扬,因为链接的答案是他开始的!!
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java