Android数据库无缝升级方案

Posted 承諾@理想國

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android数据库无缝升级方案相关的知识,希望对你有一定的参考价值。

软件迭代过程中,业务不断更新,也要求软件持续更新。相应地,数据库更新升级也是不可避免的一个环节。android作为客户端应用,数据库升级相对于服务端来说会麻烦一些。常见的升级方式有:

  1.删除旧表和数据,创建新表。优点是简单方便,缺点是丢失了旧数据。适用于应用数据依赖度低的情况。

  2.在代码中兼容处理各版本数据库,创建新表,迁移旧数据到新表。优点是保留了旧数据,缺点是需要处理兼容个版本数据库差异,比较麻烦。如果通过代码来记录维护版本差异,会导致代码臃肿且极易出错。

 

本文介绍一种简单无缝的数据库升级方案,也是属于上述的第二种方式,但是简单、高效地处理了数据库版本兼容。代码已实现在DBFramework中,这是一个轻量的数据库框架,简单、规范、高效,能够很好的处理表之间的继承关系,可以为你节省很多开发工作。

感兴趣的朋友可以了解一下。

 

数据库升级,其实是对其中的表进行升级。要更新表并且保留数据,这就必须要知道新表和旧表之间的差异。如果我们靠开发人员来记录各版本之间表的差异,无疑是一个困难和繁杂的工作。那么当我们进行数据库的时候,客户端应用有没有办法知道这些差异,知道有哪些表需要更新呢?答案是肯定的。在Sqlite中有一张叫做sqlite_master的系统表,其中记录了sqlite中的所有表信息,如下:

可以看到,其中除了表名,还有表创建语句。有了创建语句,我们就能解析出表的字段信息,然后对比新表和旧表的字段,有差异则说明表需要更新,反之则不需要更新。如下代码用来获取现有的表信息:

/**
     * Get old tables.
     * @return A map contains old tables which takes table name as key.
     */
    public Map<String, Table> getOldTables() {
        final String table = "sqlite_master";
        final String[] columns = {"name", "sql"};
        Cursor cursor = mDB.query(table, columns, "type=\'table\'", null, null, null, null);
        HashMap<String, Table> ret = null;
        if (cursor.getCount() > 0) {
            ret = new HashMap<String, Table>(cursor.getCount());
            Table tmp;
            while (cursor.moveToNext()) {
                tmp = new Table(cursor.getString(0), cursor.getString(1));
                ret.put(tmp.Name, tmp);
            }
        }
        cursor.close();
        return ret == null ? Collections.EMPTY_MAP : ret;
    }

 

Table是一个记录表信息的类,拿到现有表信息,就可以和新的表信息进行对比,主要对比表字段的变化:

  public class Table {

   ...

  public boolean equalsColumns(Table table) {
        String myColumns = getColumnsStatement(CreateSql).toUpperCase();
        String columns = getColumnsStatement(table.CreateSql).toUpperCase();
        return (myColumns.equals(columns));
    }

   ...

}

 

 如果无变化则跳过,有变化则更新,更新过程为:

1.将现有表以别名命名

2.创建新表

3.迁移数据

4.删除现有表

 如下代码所示:

 @Override
    protected void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion, List<Table> tableList) {
        Map<String, Table> oldMap = getOldTables();
        Iterator<Table> iterator = tableList.iterator();
        Table table;
        Table oldTable;
        String tempTable;
        while (iterator.hasNext()) {
            table = iterator.next();
            if ((oldTable = oldMap.get(table.Name)) == null) {
                //New table, create directly.
                db.execSQL(table.CreateSql);
                Log.i(TAG, "Table " + table.Name + " is a new table. Create it directly");
                continue;
            }
            //Remove hit table.
            oldMap.remove(table.Name);
            if (oldTable.equalsColumns(table)) {
                //Table not change.
                Log.i(TAG, "Table " + table.Name + " doesn\'t need update");
                continue;
            }
            tempTable = table.Name + TEMP_SUFFIX;
            Log.i(TAG, "Update table: " + table.Name);

            //Table changed.
            //Alter old table as temp.
            alterTableName(oldTable.Name, tempTable);
            //Create new table.
            db.execSQL(table.CreateSql);
            //Copy data.
            copyData(getCommonColumn(oldTable.getColumns(), table.getColumns()), tempTable, table.Name);
            //Delete old table
            deleteTable(tempTable);
        }
        //Delete obsolete tables not hit.
        deleteObsoleteTables(oldMap.values());
    }

 

更多细节可查看源码:DBFramework

这种方案简单方便,而且几乎一劳永逸。推荐小伙伴们使用,如果有更好的方式或者宝贵意见,欢迎交流。

 

以上是关于Android数据库无缝升级方案的主要内容,如果未能解决你的问题,请参考以下文章

Android片段不会显示

片段中的Android webView显示空白页面

在android studio中升级repo v9后,片段必须是公共静态类崩溃错误

片段中的 Xamarin Android Google 地图错误

在android中从另一个片段获取数据到片段

如何在android中的地图片段内中心线性布局?