我的应用资产文件夹中保存了一个数据库,并在应用首次打开时使用以下代码复制数据库。
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的已知问题还是有变化?
答案 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中的此类错误。
这里是:
在调用close()之后,不仅关闭与数据库的所有连接,而且还将其他数据库日志文件刷新到主.sqlite文件并删除。因此,您只有一个database.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(预写日志)。以下两个步骤是必需的。
false
在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();