Хотя ответ, который предлагает использовать: -
//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();
}
Может работать для предварительного Android Pie, где используется режим по умолчанию journal . Он не будет работать без модификации для Android Pie +, где по умолчанию используется WAL ( W обряд- A head L ogging) .
Причина в том, что getWritableDatabase делает это, она получает (открывает) базу данных, что в режиме WAL приводит к генерации и заполнению файлов -shm и -wal.
Поскольку данные в файлах не фиксируются, когда копируемая база данных открывается, первое открытие после того, как открытие копии обнаруживает, что -wal и -shm не принадлежат скопированной базе данных (вместо этого они принадлежат пустой базе данных). В результате открытая база данных является новой пустой базой данных (из-за повреждения и того, как open пытается вернуть работоспособную базу данных).
Исправления
Есть ряд исправлений.
Один из них - переопределить метод onConfigure и использовать метод SQLiteDatabase db.disableWriteAheadLogging (); для использования режима журнала. например : -
@Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
db.disableWriteAheadLogging();
}
- Приложение не использует преимущества, предлагаемые WAL, и поэтому не рекомендуется.
Другой способ - обеспечить фиксацию невыполненных транзакций. Этого можно достичь, закрыв базу данных перед выполнением копии. например используя: -
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 заключается в том, что при установке нового приложения и последующем запуске каталог data / data / package существует, но НЕ каталог data / data / package / database, и, следовательно, копирование файла завершается неудачно из-за отсутствующего каталога.
Обработка lazy / easy - это одна строка кода (но много основных строк для запуска) getWritableDatabse. Базовый код должен проверить, существует ли файл, и если он не проверяет, существуют ли родительские каталоги, и если их нет, откройте файл через открытый файл (назад) в открытом пакете SQlite SDK, который затем проходит через процесс создания заголовка файла, таблицы sqlite_master и таблицы android_metadata перед завершением работы с файлом -jorunal или файлами -wal и -shm, а затем с возвратом.
Вся эта обработка затем теряется / отменяется путем перезаписи базы данных копией из файла активов.
Эффективным способом проверки является использование File методов, чтобы увидеть, существует ли каталог database (не совсем так, как теоретически может измениться), и создать его с помощью Файл 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();
}
}
Прогон 1
с использованием кода (согласно другому ответу) на устройстве 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));
}
Работает (тот же вывод, что и в 3 и 4)
Прогон 2 - ПРОБЛЕМА
используя код как для Запустите 1 на устройстве Android 28 API (эмулятор). новая установка: -
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)
- Да, хотя база данных была скопирована (скопировано около 40 КБ), единственная существующая таблица - это android_metadata .
Пробег 3
Запуск из новой установки на устройстве 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
Пробег 4
запуск с новой установки на устройстве 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, простое решение
Приведенный выше код содержит закомментированные строки для использования предложенного 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);
}
}
- обратите внимание, что закомментированное тестирование использовало тот же файл базы данных, но с именем mytestdb и помещалось в папку assets / database (как того требует SQLiteAssetHelper).
дополнительно, чтобы использовать SQLiteAssethelper, вам нужно включить строку: -
implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:+'
в разделе зависимостей приложения build.gradle
Хотя и не поддерживается, это все же осуществимый и надежный вариант. Если вы посмотрите на код, он не использует get ???? Database, но использует методы File, чтобы проверить, существует ли база данных, а также создать каталог баз данных, если необходимо.