我想在用户首次启动应用程序时初始化应用程序数据库。
我的第一个想法是在资产中添加一个db文件,并将其复制到app db目录中。 但这似乎不可能。
因此,我的新想法是使用特定方法对所有插入语句进行编码。但是我找不到如何将整个实际数据库导出到插入语句中。
我使用DB Navigator,发现的最好是逐表导出到csv。
我不敢相信没有更好,更简单的方法...
答案 0 :(得分:1)
尽管答案提示使用:-
//in a class which extends SQLiteOpenHelper
public void importDefault() throws Exception {
String assetPath = "db/myApp.default.db";
String deviceDbDirectory;
InputStream is;
is = contexte.getAssets().open(assetPath);
deviceDbDirectory = getWritableDatabase().getPath();
writeExtractedFileToDisk(is, new FileOutputStream(deviceDbDirectory));
}
private void writeExtractedFileToDisk(InputStream in, OutputStream outs) throws IOException {
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer))>0){
outs.write(buffer, 0, length);
}
outs.flush();
outs.close();
in.close();
}
可能适用于使用默认 journal 模式的Android Pie之前的版本。如果未对Android Pie +进行修改(默认情况下使用 WAL ( W rite- A head L ogging)。
原因是getWritableDatabase这样做,它获取(打开)数据库,这在WAL模式下导致生成并填充-shm和-wal文件。
由于打开复制的数据库时未提交文件中的数据,因此在打开复制后的第一次打开检测到-wal和-shm不属于复制的数据库(它们属于空数据库)。结果是,打开的数据库是一个新的空数据库(由于损坏以及打开尝试返回可用数据库的方式)。
有许多修复程序。
一种方法是覆盖 onConfigure 方法,并使用SQLiteDatabase db.disableWriteAheadLogging(); 方法来使用日记模式。例如:-
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
db.disableWriteAheadLogging();
}
另一种方法是确保落实未完成的事务,这可以通过在进行复制之前关闭数据库来实现。例如通过使用:-
public void importDefault() throws Exception {
String assetPath = "db/myApp.default.db";
String deviceDbDirectory;
SQLiteDatabase crtdirdb;
InputStream is;
is = contexte.getAssets().open(assetPath);
deviceDbDirectory = (crtdirdb = getWritableDatabase()).getPath(); //CHANGED TO GET THE SQLITE DATABASE OBJECT
crtdirdb.close(); //ADDED TO CLOSE THE DATABASE AND THUS COMMIT TRANSACTIONS
writeExtractedFileToDisk(is, new FileOutputStream(deviceDbDirectory));
}
其优点在于,它将与pre以及Android Pie +一起使用,并保持默认或指定的日志记录模式。
不过,就像使用 getWritableDatabase 或 getReadableDatabase 的任何方法一样(后者在大多数情况下将获得前者并不重要)相对资源匮乏,因此不建议这样做,因为这样做效率很高。
另一种解决方法是删除-shm和-wal文件,这将有效地删除未完成的事务。
getWritableDatabase 或 getReadableDatabase 在历史上似乎只是用于解决没有遇到的ENOENT问题。
ENONENT是因为在安装新的App并运行时存在data / data / package /目录,但不存在data / data / package / databases目录,因此由于缺少目录而导致文件复制失败。
lazy / easy 绕行是getWritableDatabse编写的一行代码(但是要运行许多基础行)。基础代码必须检查文件是否存在,是否不检查父目录是否存在,以及是否没有创建父目录,请打开文件,然后将文件打开(返回)到SQlite SDK open,然后通过在完成管理-jorunal文件或-wal和-shm文件然后返回之前,生成文件头,sqlite_master表和android_metadata表的过程。
然后使用资产文件中的副本覆盖数据库,从而浪费/取消所有这些处理。
检查的有效方法是利用 File 方法来查看数据库(不是专门为理论上可能会更改的目录)是否存在并创建目录使用文件的 mkdirs 方法。
这样的 importDefault 方法将更加高效,适用于Android Pie +,并且在以下情况下具有更大的弹性:-
public void importDefault() throws Exception {
String assetPath = "db" + File.separator + dbname;
InputStream is = contexte.getAssets().open(assetPath);
File db = contexte.getDatabasePath(dbname);
if (!db.getParentFile().exists()) {
db.getParentFile().mkdirs();
}
writeExtractedFileToDisk(is, new FileOutputStream(db));
}
因此,覆盖SQLiteOpenHelper的完整类可能是:-
public class DBBasicAssetCopy extends SQLiteOpenHelper {
Context contexte;
static String dbname = "myApp.default.db";
SQLiteDatabase mDB;
public DBBasicAssetCopy(@Nullable Context context) {
super(context, dbname, null , 1);
this.contexte = context;
if (!(new File(contexte.getDatabasePath(dbname).getPath())).exists()) {
try {
importDefault();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error copying Asset");
}
}
mDB = this.getWritableDatabase();
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
/* Could be used as a fix
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
db.disableWriteAheadLogging();
}
*/
public void importDefault() throws Exception {
String assetPath = "db" + File.separator + dbname; //minimal hard coding
InputStream is = contexte.getAssets().open(assetPath);
File db = contexte.getDatabasePath(dbname);
if (!db.getParentFile().exists()) {
db.getParentFile().mkdirs();
}
writeExtractedFileToDisk(is, new FileOutputStream(db));
}
private void writeExtractedFileToDisk(InputStream in, OutputStream outs) throws IOException {
byte[] buffer = new byte[1024];
int length;
int totalbytes = 0;
while ((length = in.read(buffer))>0){
outs.write(buffer, 0, length);
totalbytes = totalbytes + length;
}
outs.flush();
outs.close();
in.close();
Log.d("WEFTD_TOTAL","The total bytes copied was " + String.valueOf(totalbytes));
}
}
使用(数据库作为资产):-
以及上面的 DBBasicAssetCopy 类,并使用:-
public class MainActivity extends AppCompatActivity {
//DBAssetHelper mDBhlpr;
DBBasicAssetCopy mDBHlpr002;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*
mDBhlpr = new DBAssetHelper(this);
Cursor csr = mDBhlpr.getWritableDatabase().query("sqlite_master",null,null,null,null,null,null);
while (csr.moveToNext()) {
Log.d("DBENTITIES","Name = " + csr.getString(csr.getColumnIndex("name"))
+ "Type = " + csr.getString(csr.getColumnIndex("type")) +
"SQL = " + csr.getString(csr.getColumnIndex("sql")));
}
mDBhlpr.close();
*/
mDBHlpr002 = new DBBasicAssetCopy(this);
Cursor csr = mDBHlpr002.getWritableDatabase().query("sqlite_master",null,null,null,null,null,null);
while (csr.moveToNext()) {
Log.d("DBENTITIES","Name = " + csr.getString(csr.getColumnIndex("name"))
+ "Type = " + csr.getString(csr.getColumnIndex("type")) +
"SQL = " + csr.getString(csr.getColumnIndex("sql")));
}
csr.close();
mDBHlpr002.close();
}
}
在Android 23 API设备(仿真器)上使用代码(根据其他答案)。新安装,它使用:-
public void importDefault()引发异常{
String assetPath = "db/myApp.default.db";
String deviceDbDirectory;
InputStream is;
is = contexte.getAssets().open(assetPath);
deviceDbDirectory = getWritableDatabase().getPath();
writeExtractedFileToDisk(is, new FileOutputStream(deviceDbDirectory));
}
Works(与第3和第4个输出相同)
使用与Android 28 API设备(模拟器)上的运行1 相同的代码。新安装:-
2019-06-10 12:35:21.566 13285-13285/aso.sqliteassethelpertest D/WEFTD_TOTAL: The total bytes copied was 40960
2019-06-10 12:35:21.566 13285-13285/aso.sqliteassethelpertest D/DBENTITIES: Name = android_metadataType = tableSQL = CREATE TABLE android_metadata (locale TEXT)
从Android API 23设备上的新安装运行-(仿真器):-
2019-06-10 12:08:23.842 12981-12981/aso.sqliteassethelpertest D/WEFTD_TOTAL: The total bytes copied was 40960
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = android_metadataType = tableSQL = CREATE TABLE android_metadata (locale TEXT)
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = room_master_tableType = tableSQL = CREATE TABLE room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = EntityTestTableType = tableSQL = CREATE TABLE `EntityTestTable` (`_id` INTEGER NOT NULL, `booleanPrimative` INTEGER NOT NULL, `charPrimative` INTEGER NOT NULL, `bytePrimative` INTEGER NOT NULL, `shortPrimative` INTEGER NOT NULL, `intPrimative` INTEGER NOT NULL, `longPrimative` INTEGER NOT NULL, `doublePrimative` REAL NOT NULL, `floatPrimative` REAL NOT NULL, `stringObj` TEXT, `booleanObj` INTEGER, `characterObj` INTEGER, `byteObj` INTEGER, `shortObj` INTEGER, `intObj` INTEGER, `longObj` INTEGER, `floatObj` REAL, `doubleObj` REAL, PRIMARY KEY(`_id`))
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = EVENT_SETTINGSType = tableSQL = CREATE TABLE EVENT_SETTINGS (
DBTS BIGINT NOT NULL,
DBTS_TS TIMESTAMP NOT NULL
)
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = CS_EVENT_PRICE_LISTType = tableSQL = CREATE TABLE CS_EVENT_PRICE_LIST (
EVENT_PRICE_LIST_ID INTEGER PRIMARY KEY AUTOINCREMENT,
DESCRIPTION VARCHAR (50) NOT NULL,
SALES_TAX DECIMAL (8, 2) DEFAULT 0 NOT NULL,
TAX_TYPE INTEGER DEFAULT 1 NOT NULL,
INSERTEDBY VARCHAR (50) NOT NULL,
INSTERTEDON TIMESTAMP NOT NULL,
UPDATEDBY VARCHAR (50) NOT NULL,
UPDATEDON TIMESTAMP NOT NULL,
TS BIGINT NOT NULL
)
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = sqlite_sequenceType = tableSQL = CREATE TABLE sqlite_sequence(name,seq)
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = ti_CS_EVENT_PRICE_LIST_standardType = triggerSQL = CREATE TRIGGER ti_CS_EVENT_PRICE_LIST_standard BEFORE INSERT ON CS_EVENT_PRICE_LIST
WHEN (SELECT Enabled FROM TriggerControl WHERE TriggerType = 'productTables')
BEGIN
INSERT INTO CS_EVENT_PRICE_LIST ( Description, Sales_Tax, Tax_Type, insertedby, instertedon, updatedby, updatedon, ts)
VALUES (new.Description, new.Sales_Tax, new.Tax_Type,
new.insertedby,
COALESCE(new.instertedon, julianday('now')),
COALESCE(new.updatedby, new.insertedby),
COALESCE(new.updatedon, julianday('now')),
(select DBTS + 1 from EVENT_SETTINGS where rowid = 1));
UPDATE EVENT_SETTINGS
SET DBTS = (select ts from CS_EVENT_PRICE_LIST where Description = new.Description),
DBTS_TS = julianday('now')
WHERE rowid = 1;
SELECT RAISE(IGNORE);
END
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = TriggerControlType = tableSQL = CREATE TABLE TriggerControl (Enabled INTEGER, TriggerType TEXT)
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = buchungType = tableSQL = CREATE TABLE buchung (ID INTEGER PRIMARY KEY,othercolumn TEXT, Datum TEXT)
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = update_buchungType = triggerSQL = CREATE TRIGGER update_buchung BEFORE UPDATE ON Buchung
WHEN old.Datum IS NOT NULL
BEGIN
SELECT RAISE(FAIL, "UPDATE NOT ALLOWED");
END
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = insert_buchungType = triggerSQL = CREATE TRIGGER insert_buchung AFTER INSERT ON Buchung
BEGIN
update Buchung SET Datum = datetime('now') WHERE ID = NEW.ID;
END
通过Android 28设备(模拟器)上的新安装运行:-
2019-06-10 12:08:23.842 12981-12981/aso.sqliteassethelpertest D/WEFTD_TOTAL: The total bytes copied was 40960
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = android_metadataType = tableSQL = CREATE TABLE android_metadata (locale TEXT)
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = room_master_tableType = tableSQL = CREATE TABLE room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)
2019-06-10 12:08:23.847 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = EntityTestTableType = tableSQL = CREATE TABLE `EntityTestTable` (`_id` INTEGER NOT NULL, `booleanPrimative` INTEGER NOT NULL, `charPrimative` INTEGER NOT NULL, `bytePrimative` INTEGER NOT NULL, `shortPrimative` INTEGER NOT NULL, `intPrimative` INTEGER NOT NULL, `longPrimative` INTEGER NOT NULL, `doublePrimative` REAL NOT NULL, `floatPrimative` REAL NOT NULL, `stringObj` TEXT, `booleanObj` INTEGER, `characterObj` INTEGER, `byteObj` INTEGER, `shortObj` INTEGER, `intObj` INTEGER, `longObj` INTEGER, `floatObj` REAL, `doubleObj` REAL, PRIMARY KEY(`_id`))
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = EVENT_SETTINGSType = tableSQL = CREATE TABLE EVENT_SETTINGS (
DBTS BIGINT NOT NULL,
DBTS_TS TIMESTAMP NOT NULL
)
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = CS_EVENT_PRICE_LISTType = tableSQL = CREATE TABLE CS_EVENT_PRICE_LIST (
EVENT_PRICE_LIST_ID INTEGER PRIMARY KEY AUTOINCREMENT,
DESCRIPTION VARCHAR (50) NOT NULL,
SALES_TAX DECIMAL (8, 2) DEFAULT 0 NOT NULL,
TAX_TYPE INTEGER DEFAULT 1 NOT NULL,
INSERTEDBY VARCHAR (50) NOT NULL,
INSTERTEDON TIMESTAMP NOT NULL,
UPDATEDBY VARCHAR (50) NOT NULL,
UPDATEDON TIMESTAMP NOT NULL,
TS BIGINT NOT NULL
)
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = sqlite_sequenceType = tableSQL = CREATE TABLE sqlite_sequence(name,seq)
2019-06-10 12:08:23.848 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = ti_CS_EVENT_PRICE_LIST_standardType = triggerSQL = CREATE TRIGGER ti_CS_EVENT_PRICE_LIST_standard BEFORE INSERT ON CS_EVENT_PRICE_LIST
WHEN (SELECT Enabled FROM TriggerControl WHERE TriggerType = 'productTables')
BEGIN
INSERT INTO CS_EVENT_PRICE_LIST ( Description, Sales_Tax, Tax_Type, insertedby, instertedon, updatedby, updatedon, ts)
VALUES (new.Description, new.Sales_Tax, new.Tax_Type,
new.insertedby,
COALESCE(new.instertedon, julianday('now')),
COALESCE(new.updatedby, new.insertedby),
COALESCE(new.updatedon, julianday('now')),
(select DBTS + 1 from EVENT_SETTINGS where rowid = 1));
UPDATE EVENT_SETTINGS
SET DBTS = (select ts from CS_EVENT_PRICE_LIST where Description = new.Description),
DBTS_TS = julianday('now')
WHERE rowid = 1;
SELECT RAISE(IGNORE);
END
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = TriggerControlType = tableSQL = CREATE TABLE TriggerControl (Enabled INTEGER, TriggerType TEXT)
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = buchungType = tableSQL = CREATE TABLE buchung (ID INTEGER PRIMARY KEY,othercolumn TEXT, Datum TEXT)
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = update_buchungType = triggerSQL = CREATE TRIGGER update_buchung BEFORE UPDATE ON Buchung
WHEN old.Datum IS NOT NULL
BEGIN
SELECT RAISE(FAIL, "UPDATE NOT ALLOWED");
END
2019-06-10 12:08:23.849 12981-12981/aso.sqliteassethelpertest D/DBENTITIES: Name = insert_buchungType = triggerSQL = CREATE TRIGGER insert_buchung AFTER INSERT ON Buchung
BEGIN
update Buchung SET Datum = datetime('now') WHERE ID = NEW.ID;
END
上面的代码包括注释掉的行,用于使用建议的SQLIteAssethelper,它适用于pre和Android Pie +,在这种情况下,更简单的Database Helper是:-
public class DBAssetHelper extends SQLiteAssetHelper {
public static final String DBNAME = "mytestdb";
public static final int DBVERSION = 1;
public DBAssetHelper(Context context) {
super(context, DBNAME, null, null, DBVERSION);
}
}
要使用SQLiteAssethelper,还需要包括以下行:-
implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:+'
尽管不受支持,但它仍然是可行且合理的选择。如果您看一下代码,它不使用get?Database,而是使用File方法检查数据库是否存在,并在需要时创建数据库目录。
答案 1 :(得分:-1)
感谢CommonsWare和SQLiteAssetHelper,这是一个简化的代码,可将db文件从资产复制到应用程序。
//in a class which extends SQLiteOpenHelper
public void importDefault() throws Exception {
String assetPath = "db/myApp.default.db";
String deviceDbDirectory;
InputStream is;
is = contexte.getAssets().open(assetPath);
deviceDbDirectory = getWritableDatabase().getPath();
writeExtractedFileToDisk(is, new FileOutputStream(deviceDbDirectory));
}
private void writeExtractedFileToDisk(InputStream in, OutputStream outs) throws IOException {
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer))>0){
outs.write(buffer, 0, length);
}
outs.flush();
outs.close();
in.close();
}