53

我在我的应用程序资产文件夹中保存了一个数据库,并在应用程序首次打开时使用以下代码复制数据库。

inputStream = mContext.getAssets().open(Utils.getDatabaseName());

        if(inputStream != null) {

            int mFileLength = inputStream.available();

            String filePath = mContext.getDatabasePath(Utils.getDatabaseName()).getAbsolutePath();

            // Save the downloaded file
            output = new FileOutputStream(filePath);

            byte data[] = new byte[1024];
            long total = 0;
            int count;
            while ((count = inputStream.read(data)) != -1) {
                total += count;
                if(mFileLength != -1) {
                    // Publish the progress
                    publishProgress((int) (total * 100 / mFileLength));
                }
                output.write(data, 0, count);
            }
            return true;
        }

上面的代码运行没有问题,但是当您尝试查询数据库时,您会得到一个 SQLite: No such table 异常。

此问题仅在 Android P 中出现,所有早期版本的 Android 都可以正常工作。

这是 Android P 的一个已知问题还是发生了一些变化?

4

15 回答 15

66

有一个类似的问题,并解决了这个添加到我的 SQLiteOpenHelper

    @Override
    public void onOpen(SQLiteDatabase db) {
        super.onOpen(db);
        db.disableWriteAheadLogging();
    }

显然,Android P 设置了 PRAGMA Log 的东西不同。仍然不知道是否会产生副作用,但似乎有效!

于 2018-08-21T17:34:24.940 回答
36

我的 Android P 问题通过在 createDataBase() 方法中的 this.getReadableDatabase() 之后添加“this.close()”得到解决,如下所示。

private void createDataBase() throws IOException {
    this.getReadableDatabase();
    this.close(); 
    try {           
        copyDataBase();            
    } catch (IOException e) {           
        throw new RuntimeException(e);
    }
}
于 2018-11-12T08:50:20.180 回答
29

与以前的版本相比,这个问题在 Android P 上似乎更频繁地导致崩溃,但这并不是 Android P 本身的错误。

问题是您将值分配给您的行String filePath打开了与数据库的连接,当您从资产复制文件时该连接保持打开状态。

要解决问题,请更换行

String filePath = mContext.getDatabasePath(Utils.getDatabaseName()).getAbsolutePath();

使用代码获取文件路径值,然后关闭数据库:

MySQLiteOpenHelper helper = new MySQLiteOpenHelper();
SQLiteDatabase database = helper.getReadableDatabase();
String filePath = database.getPath();
database.close();

并且还添加了一个内部辅助类:

class MySQLiteOpenHelper extends SQLiteOpenHelper {

    MySQLiteOpenHelper(Context context, String databaseName) {
        super(context, databaseName, null, 2);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}
于 2018-05-31T18:35:12.980 回答
9

我遇到了类似的问题。我正在复制数据库,但不是从资产中复制。我发现这个问题根本与我的数据库文件复制代码无关。它也与打开、未关闭、刷新或同步的文件无关。我的代码通常会覆盖现有的未打开数据库。Android Pie 似乎是新的/不同的,并且与以前的 Android 版本不同的是,当 Android Pie 创建 SQLite 数据库时,它默认将 journal_mode 设置为 WAL(预写日志记录)。我从来没有使用过 WAL 模式,SQLite 文档说 journal_mode 默认应该是 DELETE。问题是如果我覆盖现有的数据库文件,我们称之为 my.db,预写日志 my.db-wal 仍然存在,并且有效地“覆盖”了新复制的 my.db 文件中的内容。当我打开我的数据库时,sqlite_master 表通常只包含一行 android_metadata。我期待的所有桌子都不见了。我的解决方案是在打开数据库后简单地将 journal_mode 设置回 DELETE,尤其是在使用 Android Pie 创建新数据库时。

PRAGMA 日志模式=删除;

也许 WAL 更好,并且可能有某种方法可以关闭数据库,这样预写日志就不会妨碍您,但我并不真正需要 WAL,并且对于所有以前的 Android 版本都不需要它。

于 2018-08-16T21:48:39.300 回答
4

不幸的是,在非常具体的情况下,公认的答案只是“碰巧起作用”,但它并没有给出始终如一的工作建议来避免 Android 9 中出现此类错误。

这里是:

  1. 在您的应用程序中拥有 SQLiteOpenHelper 类的单个实例来访问您的数据库。
  2. 如果您需要重写/复制数据库,请使用此实例的 SQLiteOpenHelper.close() 方法关闭数据库(并关闭与此数据库的所有连接),并且不再使用此 SQLiteOpenHelper 实例。

调用 close() 后,不仅所有与数据库的连接都被关闭,而且额外的数据库日志文件被刷新到主 .sqlite 文件并被删除。所以你只有一个 database.sqlite 文件,可以重写或复制。

  1. 在复制/重写等之后,创建一个新的 SQLiteOpenHelper 单例,其中 getWritableDatabase() 方法将返回 SQLite 数据库的新实例!并使用它直到下次您需要复制/重写您的数据库...

这个答案帮助我弄清楚:https ://stackoverflow.com/a/35648781/297710

我在我的 AndStatus 应用程序https://github.com/andstatus/andstatus中的 Android 9 中遇到了这个问题,它有相当大的自动化测试套件,在此提交之前一直在 Android 9 模拟器中重现“SQLiteException:没有这样的表”: https: //github.com/andstatus/andstatus/commit/1e3ca0eee8c9fbb8f6326b72dc4c393143a70538所以如果你真的很好奇,你可以在这次提交之前和之后运行所有测试来看看有什么不同。

于 2019-01-16T20:32:37.540 回答
2

不禁用 WAL 的解决方案

Android 9引入了一种称为Compatibility WAL(预写登录)的 SQLiteDatabase 特殊模式,该模式允许数据库使用“journal_mode=WAL”,同时保留每个数据库最多保持一个连接的行为。

详细信息:
https ://source.android.com/devices/tech/perf/compatibility-wal

SQLite WAL模式在这里详细解释:
https ://www.sqlite.org/wal.html

在官方文档中,WAL 模式添加了第二个数据库文件,名为databasename和“-wal”。因此,如果您的数据库名为“data.db”,则它在同一目录中称为“data-wal.db”。

现在的解决方案是在 Android 9 上保存和恢复这两个文件(data.db 和 data-wal.db)。

之后它就像在早期版本中一样工作。

于 2019-01-05T18:27:29.357 回答
2

我有同样的事情,我在 android 版本 4 中有一个应用程序,当更新我的具有 android 9 的手机时,我花了 2 天时间试图找到错误,感谢我的情况下的评论,我只需要添加这个。关 ();

private void createDataBase () throws IOException {
     this.getReadableDatabase ();
     this.close ();
     try {
         copyDataBase ();
     } catch (IOException e) {
         throw new RuntimeException (e);
     }
}

准备好运行所有版本!

于 2019-11-08T21:14:22.230 回答
1

类似的问题,只有 Android P 设备受到影响。所有以前的版本都没有问题。

在 Android 9 设备上关闭自动恢复。

我们这样做是为了排除故障。不推荐用于生产案例。

在数据库助手中调用复制数据库函数之前,自动恢复将数据库文件的副本放置在数据目录中。因此 a file.exists() 返回 true。

从开发设备备份的数据库缺少表。因此“找不到表”实际上是正确的。

于 2018-09-25T17:42:33.083 回答
1

首先,感谢您发布这个问题。我也发生了同样的事情。一切运行良好,但是在针对 Android P Preview 进行测试时,我遇到了崩溃。这是我为此代码发现的错误:

private void copyDatabase(File dbFile, String db_name) throws IOException{
    InputStream is = null;
    OutputStream os = null;

    SQLiteDatabase db = context.openOrCreateDatabase(db_name, Context.MODE_PRIVATE, null);
    db.close();
    try {
        is = context.getAssets().open(db_name);
        os = new FileOutputStream(dbFile);

        byte[] buffer = new byte[1024];
        while (is.read(buffer) > 0) {
            os.write(buffer);
        }
    } catch (IOException e) {
        e.printStackTrace();
        throw(e);
    } finally {
        try {
            if (os != null) os.close();
            if (is != null) is.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

我遇到的问题是这段代码工作得很好,但在 SDK 28+ openOrCreateDatabase 中不再自动为您创建 android_metadata 表。因此,如果您执行“select * from TABLE”的查询,它将找不到该 TABLE,因为该查询开始关注应该是元数据表的“第一个”表。我通过手动添加 android_metadata 表解决了这个问题,一切都很好。希望其他人觉得这很有用。花了很长时间才弄清楚,因为特定的查询仍然可以正常工作。

于 2018-08-13T15:08:37.780 回答
1

这是这个问题的完美解决方案:

只需在您的SQLiteOpenHelper类中覆盖此方法:

@Override
public void onOpen(SQLiteDatabase db) {
    super.onOpen(db);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        db.disableWriteAheadLogging();
    }
}
于 2019-07-24T13:02:08.173 回答
0

在版本 P 中,主要变化是 WAL(预写日志)。需要以下两个步骤。

  1. 通过资源下的值文件夹中的 config.xml 中的以下行禁用相同功能。

错误的

  1. 在 createDatabase 方法中的 DBAdapter 类中进行以下更改。否则,具有早期 Android 版本的手机会崩溃。

    私有 void createDataBase() 抛出 IOException {

    if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.P) {
                    this.getWritableDatabase();
        try {           
            copyDataBase();            
        } catch (IOException e) {           
            throw new RuntimeException(e);
        }
    }
    

    }

于 2019-08-15T07:54:10.777 回答
0

我无法对已接受的答案发表评论,因此我必须打开一个新答案。

mContext.getDatabasePath() 不会打开数据库连接,它甚至不需要现有的文件名即可成功(请参阅sources/android-28/android/app/ContextImpl.java):

@Override
public File getDatabasePath(String name) {
    File dir;
    File f;

    if (name.charAt(0) == File.separatorChar) {
        // snip
    } else {
        dir = getDatabasesDir();
        f = makeFilename(dir, name);
    }

    return f;
}

private File makeFilename(File base, String name) {
    if (name.indexOf(File.separatorChar) < 0) {
        return new File(base, name);
    }
    throw new IllegalArgumentException(
            "File " + name + " contains a path separator");
}
于 2019-08-01T07:16:36.340 回答
0

在 Android PIE 及以上版本中使用以下行作为数据库文件路径的最简单答案:

DB_NAME="xyz.db";
DB_Path = "/data/data/" + BuildConfig.APPLICATION_ID + "/databases/"+DB_NAME;
于 2019-05-08T17:10:33.073 回答
0

Android Pie中出现的问题,解决方案是:

 SQLiteDatabase db = this.getReadableDatabase();
        if (db != null && db.isOpen())
            db.close();
   copyDataBase();
于 2019-12-05T14:06:21.447 回答
0

看来您没有关闭输出流。虽然它可能无法解释为什么没有真正创建数据库(除非 Android P 添加了多 MB 缓冲区),但使用 try-with-resource 是一个好习惯,例如:

// garantees that the data are flushed and the resources freed
try (FileOutputStream output = new FileOutputStream(filePath)) {
    byte data[] = new byte[1024];
    long total = 0;
    int count;
    while ((count = inputStream.read(data)) != -1) {
        total += count;
        if (mFileLength != -1) {
            // Publish the progress
            publishProgress((int) (total * 100 / mFileLength));
        }
        output.write(data, 0, count);
    }

    // maybe a bit overkill
    output.getFD().sync();
}
于 2018-05-28T12:25:21.243 回答