首次运行时初始化数据库

时间:2019-06-09 15:09:08

标签: android database sqlite initialization

我想在用户首次启动应用程序时初始化应用程序数据库。

我的第一个想法是在资产中添加一个db文件,并将其复制到app db目录中。 但这似乎不可能。

因此,我的新想法是使用特定方法对所有插入语句进行编码。但是我找不到如何将整个实际数据库导出到插入语句中。

我使用DB Navigator,发现的最好是逐表导出到csv。

我不敢相信没有更好,更简单的方法...

2 个答案:

答案 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();
}
  • 该应用程序没有利用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是因为在安装新的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));
} 
  • 请注意 dbname 被设置为该类的类变量。

  • 数据库具有以下表:-

    • enter image description here
  • 以及以下触发器:-

    • enter image description here

因此,覆盖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));
    }
}

另一种解决方法

示例(证明)

使用(数据库作为资产):-

enter image description here

以及上面的 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));
}

Works(与第3和第4个输出相同)

运行2-问题

使用与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)
  • 是的,即使已复制数据库(已复制大约40kB),存在的唯一表仍是 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 ,并放置在asset / databases文件夹中(如SQLiteAssetHelper所要求)。
  • 要使用SQLiteAssethelper,还需要包括以下行:-

    • implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:+'
  • 在应用程序 build.gradle

    的依赖项部分中的
  • 尽管不受支持,但它仍然是可行且合理的选择。如果您看一下代码,它不使用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();
    }