Android P - ' SQLite:没有这样的表错误'从资产复制数据库后

时间:2018-05-22 21:34:43

标签: android sqlite android-9.0-pie

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

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

此问题仅发生在Android P中,所有早期版本的Android都能正常运行。

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

15 个答案:

答案 0 :(得分:45)

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

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

显然,Android P将PRAGMA Log设置为不同。仍然不知道是否会有副作用,但似乎正在起作用!

答案 1 :(得分:24)

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

答案 2 :(得分:21)

我的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);
    }
}

答案 3 :(得分:6)

我遇到了类似的问题。我正在复制数据库,但不是从资产复制。我发现问题完全与数据库文件复制代码无关。它也与打开,未关闭,刷新或同步的文件无关。我的代码通常会覆盖现有的未打开数据库。与Android Pie相比,Android Pie似乎是新的/不同的是,当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 journal_mode = DELETE;

也许WAL更好,也许可以用某种方法关闭数据库,以免预写日志成为障碍,但我实际上并不需要WAL,并且所有以前的Android版本都不需要WAL 。

答案 4 :(得分: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表。因此,如果您执行“从表中选择*”查询,它将找不到该表,因为该查询开始寻找应为元数据表的“第一”表。我通过手动添加android_metadata表来解决此问题,一切都很好。希望其他人觉得这有用。花了很长时间才弄清楚,因为特定的查询仍然可以正常工作。

答案 5 :(得分:1)

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

在Android 9设备上关闭自动还原。

我们这样做是为了进行故障排除。不建议用于生产案例。

在数据库帮助器中调用复制数据库功能之前,自动恢复是将数据库文件的副本放置在数据目录中。因此,一个file.exists()返回true。

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

答案 6 :(得分:1)

无需禁用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上保存和恢复 BOTH 文件(data.db和data-wal.db)。

此后,它将像早期版本一样工作。

答案 7 :(得分:1)

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

这里是:

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

在调用close()之后,不仅关闭与数据库的所有连接,而且还将其他数据库日志文件刷新到主.sqlite文件并删除。因此,您只有一个database.sqlite文件,可以重写或复制。

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

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

我在Android 9中的AndStatus应用程序https://github.com/andstatus/andstatus中遇到了这个问题,该应用程序具有相当大的自动化测试套件,该测试在提交之前始终在Android 9仿真器中始终复制“ SQLiteException:无此类表”: https://github.com/andstatus/andstatus/commit/1e3ca0eee8c9fbb8f6326b72dc4c393143a70538因此,如果您真的很好奇,可以在此提交之前和之后运行“所有”测试,以查看有所不同。

答案 8 :(得分:0)

您似乎没有关闭输出流。虽然它可能无法解释为什么没有真正创建db(除非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();
}

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

这是解决此问题的完美方法:

只需在您的sorted()类中重写此方法:

SQLiteOpenHelper

答案 11 :(得分: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");
}

答案 12 :(得分:0)

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

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

false

  1. 在createDatabase方法中的DBAdapter类中进行以下更改。否则,具有早期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);
        }
    }
    

    }

答案 13 :(得分:0)

我有同样的事情,我在android第4版中有一个应用程序,并且更新具有android 9的手机时,我花了2天的时间来尝试查找错误,感谢我的情况下的评论添加this.close();

// Copy received data into packet at current offset
Span<byte> destination = Packet.AsSpan().Slice(Received, buffer.Length);
Span<byte> source = buffer.Span;
source.CopyTo(destination);
Received += buffer.Length;

所有版本均已准备就绪!!

答案 14 :(得分:0)

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

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