我在项目中使用预先填充的数据库。我有一个创建的.sql基础,并在第一次启动时将其复制。该基础大33mb。
private void copyDataBase() throws IOException {
InputStream externalDbStream = context.getAssets().open(DB_NAME);
String outFileName = DB_PATH + DB_NAME;
OutputStream localDbStream = new FileOutputStream(outFileName);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = externalDbStream.read(buffer)) > 0) {
localDbStream.write(buffer, 0, bytesRead);
}
localDbStream.close();
externalDbStream.close();
}
它在API 28以外的其他Android版本上都可以正常工作。API28引发“原因:android.database.sqlite.SQLiteException:无此类表:短语”例外:
Caused by: android.database.sqlite.SQLiteException: no such table: phrases (code 1 SQLITE_ERROR): , while compiling: select * from phrases where complexity > 1 and known is null or known == 1 order by complexity limit 10
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:903)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:514)
at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
at android.database.sqlite.SQLiteQuery.<init>(SQLiteQuery.java:37)
at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:46)
谢谢。
答案 0 :(得分:3)
使用SQLite并复制现有数据库突然不支持API 28的应用的典型原因是,要解决 database 文件夹不存在的问题(副本如果该目录不存在,将失败)是创建一个空数据库然后覆盖该数据库。
但是,默认情况下,默认情况下,从API 28开始,SDK使用WAL(预写日志记录),并且创建要覆盖的空数据库会导致-shm和-wal文件被删除创建。这些文件的存在导致复制后数据库为空。
一种快速但不建议使用的修复方法是,在将SQLiteOpenHelper子类化的类中重写 onConfigure 方法以使用disableWriteAheadLogging方法,以便以日志模式打开数据库。 / p>
为了从benefits of WAL获得收益,推荐的方法是检查数据库目录是否存在,如果目录不存在则创建该目录,而不是创建要覆盖的数据库(因此复制数据库时-shm和-wal文件不存在)
下面是一个示例方法,其中在检查数据库是否存在时检查/创建目录( 显然需要相应地调整 ):-
private boolean checkDataBase() {
/**
* Does not open the database instead checks to see if the file exists
* also creates the databases directory if it does not exists
* (the real reason why the database is opened, which appears to result in issues)
*/
File db = new File(myContext.getDatabasePath(DB_NAME).getPath()); //Get the file name of the database
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;
}
以上内容摘自SQLiteOpenHelper的以下子类:-
public class DBHelper extends SQLiteOpenHelper {
private static String DB_NAME = "db";
private SQLiteDatabase myDataBase;
private final Context myContext;
private int bytes_copied = 0;
private static int buffer_size = 1024;
private int blocks_copied = 0;
public DBHelper(Context context) {
super(context, DB_NAME, null, 1);
this.myContext = context;
// Check for and create (copy DB from assets) when constructing the DBHelper
if (!checkDataBase()) {
bytes_copied = 0;
blocks_copied = 0;
createDataBase();
}
}
/**
* Creates an empty database on the system and rewrites it with your own database.
* */
public void createDataBase() {
boolean dbExist = checkDataBase(); // Double check
if(dbExist){
//do nothing - database already exists
} else {
//By calling this method an empty database will be created into the default system path
//of your application so we are gonna be able to overwrite that database with our database.
//this.getReadableDatabase();
//<<<<<<<<<< Dimsiss the above comment
//By calling this method an empty database IS NOT created nor are the related -shm and -wal files
//The method that creates the database is flawed and was only used to resolve the issue
//of the copy failing in the absence of the databases directory.
//The dbExist method, now utilised, checks for and creates the database directory, so there
//is then no need to create the database just to create the databases library. As a result
//the -shm and -wal files will not exist and thus result in the error associated with
//Android 9+ failing with due to tables not existining after an apparently successful
//copy.
try {
copyDataBase();
} catch (IOException e) {
File db = new File(myContext.getDatabasePath(DB_NAME).getPath());
if (db.exists()) {
db.delete();
}
e.printStackTrace();
throw new RuntimeException("Error copying database (see stack-trace above)");
}
}
}
/**
* Check if the database already exist to avoid re-copying the file each time you open the application.
* @return true if it exists, false if it doesn't
*/
private boolean checkDataBase() {
/**
* Does not open the database instead checks to see if the file exists
* also creates the databases directory if it does not exists
* (the real reason why the database is opened, which appears to result in issues)
*/
File db = new File(myContext.getDatabasePath(DB_NAME).getPath()); //Get the file name of the database
Log.d("DBPATH","DB Path is " + db.getPath()); //TODO remove for Live 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;
}
/**
* Copies your database from your local assets-folder to the just created empty database in the
* system folder, from where it can be accessed and handled.
* This is done by transfering bytestream.
* */
private void copyDataBase() throws IOException {
final String TAG = "COPYDATABASE";
//Open your local db as the input stream
Log.d(TAG,"Initiated Copy of the database file " + DB_NAME + " from the assets folder."); //TODO remove for Live App
InputStream myInput = myContext.getAssets().open(DB_NAME); // Open the Asset file
String dbpath = myContext.getDatabasePath(DB_NAME).getPath();
Log.d(TAG,"Asset file " + DB_NAME + " found so attmepting to copy to " + dbpath); //TODO remove for Live App
// Path to the just created empty db
//String outFileName = DB_PATH + DB_NAME;
//Open the empty db as the output stream
File outfile = new File(myContext.getDatabasePath(DB_NAME).toString());
Log.d("DBPATH","path is " + outfile.getPath()); //TODO remove for Live App
//outfile.setWritable(true); // NOT NEEDED as permission already applies
//OutputStream myoutputx2 = new FileOutputStream(outfile);
/* Note done in checkDatabase method
if (!outfile.getParentFile().exists()) {
outfile.getParentFile().mkdirs();
}
*/
OutputStream myOutput = new FileOutputStream(outfile);
//transfer bytes from the inputfile to the outputfile
byte[] buffer = new byte[buffer_size];
int length;
while ((length = myInput.read(buffer))>0) {
blocks_copied++;
Log.d(TAG,"Ateempting copy of block " + String.valueOf(blocks_copied) + " which has " + String.valueOf(length) + " bytes."); //TODO remove for Live App
myOutput.write(buffer, 0, length);
bytes_copied += length;
}
Log.d(TAG,
"Finished copying Database " + DB_NAME +
" from the assets folder, to " + dbpath +
String.valueOf(bytes_copied) + "were copied, in " +
String.valueOf(blocks_copied) + " blocks of size " +
String.valueOf(buffer_size) + "."
); //TODO remove for Live App
//Close the streams
myOutput.flush();
myOutput.close();
myInput.close();
Log.d(TAG,"All Streams have been flushed and closed."); //TODO remove for Live App
}
@Override
public synchronized void close() {
if(myDataBase != null)
myDataBase.close();
super.close();
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
Log.d("DBCONFIGURE","Database has been configured "); //TODO remove for Live App
//db.disableWriteAheadLogging(); //<<<<<<<<<< un-comment to force journal mode
}
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
Log.d("DBOPENED","Database has been opened."); //TODO remove for live App
}
}