在Oreo上复制SQLite数据库

时间:2019-05-21 15:24:06

标签: 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中是否有任何更改由于某种原因导致失败?可能是什么原因造成的?

3 个答案:

答案 0 :(得分:2)

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;
}

答案 1 :(得分:1)

是的,对于android 9,SQLite数据库中还有两个其他文件,如果您复制覆盖现有数据库的数据库,并且不先删除其他两个文件,则该数据库将认为自身已损坏。查看此答案:Ship android app with pre populated database

但是@MikeT应该获得荣誉,因为链接的答案是他的开始!

答案 2 :(得分:0)

我不确定,直到我看到logcat。但是,我认为您在访问数据库时可能缺少一些权限。

我想问一下,您的AndroidManifest.xml文件中是否有以下提供程序,用于授权您的应用程序访问数据库。这是sample manifest file个提供者。

<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;
    }
}

我有一个sample code in Github来介绍Android中的基本数据库操作,您也可以看看。