Android P - 'SQLite: No Such Table Error' 从资产复制数据库后

Android P - 'SQLite: No Such Table Error' after copying database from assets

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

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:没有这样的 table 异常。

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

这是 Android P 的已知问题还是有什么变化?

看来你没有关闭输出流。虽然它可能无法解释为什么没有真正创建数据库(除非 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();
}

这个问题似乎导致 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:

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) {
    }
}

首先,感谢您提出这个问题。我有同样的事情发生。一切都运行良好,但是当针对 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 table。因此,如果您查询 "select * from TABLE",它将找不到 TABLE,因为查询开始关注 "first" table,这应该是元数据 table .我通过手动添加 android_metadata table 解决了这个问题,一切都很好。希望其他人发现这很有用。花了很长时间才弄清楚,因为特定的查询仍然可以正常工作。

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

创建新数据库时

杂注journal_mode=删除;

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

遇到了类似的问题,并将其添加到我的 SQLiteOpenHelper 中解决了这个问题

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

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

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

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

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

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

从开发设备备份的数据库缺少 table。因此 "no table found" 实际上是正确的。

我关于 Android P 的问题通过添加解决了 'this.close()' 在 createDataBase() 方法中的 this.getReadableDatabase() 之后,如下所示。

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

不关闭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.

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

不幸的是,接受的答案只是 "happens to work" 在非常具体的情况下,但它并没有给出一个始终如一的工作建议来避免 Android 9.

中的此类错误

这里是:

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

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

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

这个答案帮助我弄明白了:

我在我的 AndStatus 应用程序 Android 9 中遇到了这个问题 https://github.com/andstatus/andstatus 它有相当大的自动化测试套件,之前在 Android 9 模拟器中始终如一地重现 "SQLiteException: no such table"这个承诺: https://github.com/andstatus/andstatus/commit/1e3ca0eee8c9fbb8f6326b72dc4c393143a70538 所以如果你真的很好奇,你可以 运行 在此提交之前和之后的所有测试以查看差异。

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

DB_NAME="xyz.db";
DB_Path = "/data/data/" + BuildConfig.APPLICATION_ID + "/databases/"+DB_NAME;

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

只需在您的 SQLiteOpenHelper class:

中覆盖此方法
@Override
public void onOpen(SQLiteDatabase db) {
    super.onOpen(db);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        db.disableWriteAheadLogging();
    }
}

在版本P中,主要变化是WAL(Write Ahead Log)。需要以下两个步骤。

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

错误

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

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

    }

我在 android 的第 4 版应用程序中遇到了同样的事情,并且在更新具有 android 9 的手机时,我花了 2 天时间试图找出错误,谢谢对于我的评论,我只需要添加 this.close ();

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

准备好运行所有版本!!

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

 SQLiteDatabase db = this.getReadableDatabase();
        if (db != null && db.isOpen())
            db.close();
   copyDataBase();