实现SqliteOpenHelper

我们SqliteOpenHelper的基本组成是数据库的创建和更新,并提供了一个SqliteDatebase,使得我们可以用它来工作。查询可以被抽取出来放在其它的类中:

class ForecastDbHelper() : ManagedSQLiteOpenHelper(App.instance,
        ForecastDbHelper.DB_NAME, null, ForecastDbHelper.DB_VERSION) {
    ...
}

我们在前面的章节中使用过我们创建的App.instance,这次我们同样的包括数据库名称和版本。这些值我们都会与SqliteOpenHelper一起定义在companion object中:

companion object {
    val DB_NAME = "forecast.db"
    val DB_VERSION = 1
    val instance: ForecastDbHelper by lazy { ForecastDbHelper() }
}

instance这个属性使用了lazy委托,它表示直到它真的被调用才会被创建。用这种方法,如果数据库从来没有被使用,我们没有必要去创建这个对象。一般lazy委托的代码块可以阻止在多个不同的线程中创建多个对象。这个只会发生在两个线程在同事时间访问这个instance对象,它很难发生但是发生具体还有看app的实现。无人如何,lazy委托是线程安全的。

为了去创建这些定义的表,我们需要去提供一个onCreate函数的实现。当没有库使用的时候,创建表会通过我们编写原生的包含我们定义好的列和类型的CREATE TABLE语句来实现。然而Anko提供了一个简单的扩展函数,它接收一个表的名字和一系列由列名和类型构建的Pair对象:

db.createTable(CityForecastTable.NAME, true,
        Pair(CityForecastTable.ID, INTEGER + PRIMARY_KEY),
        Pair(CityForecastTable.CITY, TEXT),
        Pair(CityForecastTable.COUNTRY, TEXT))
  • 第一个参数是表的名称
  • 第二个参数,当是true的时候,创建之前会检查这个表是否存在。
  • 第三个参数是一个Pair类型的vararg参数。vararg也存在在Java中,这是一种在一个函数中传入联系很多相同类型的参数。这个函数也接收一个对象数组。

Anko中有一种叫做SqlType的特殊类型,它可以与SqlTypeModifiers混合,比如PRIMARY_KEY+操作符像之前那样被重写了。这个plus函数会把两者通过合适的方式结合起来,然后返回一个新的SqlType

fun SqlType.plus(m: SqlTypeModifier) : SqlType {
    return SqlTypeImpl(name, if (modifier == null) m.toString()
            else "$modifier $m")
}

如你所见,她会把多个修饰符组合起来。

但是回到我们的代码,我们可以修改得更好。Kotlin标准库中包含了一个叫to的函数,又一次,让我们来展示Kotlin的强大之处。它作为第一参数的扩展函数,接收另外一个对象作为参数,把两者组装并返回一个Pair

public fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

因为带有一个函数参数的函数可以被用于inline,所以结果非常清晰:

val pair = object1 to object2

然后,把他们应用到表的创建中:

db.createTable(CityForecastTable.NAME, true,
        CityForecastTable.ID to INTEGER + PRIMARY_KEY,
        CityForecastTable.CITY to TEXT,
        CityForecastTable.COUNTRY to TEXT)

这就是整个函数看起来的样子:

override fun onCreate(db: SQLiteDatabase) {
    db.createTable(CityForecastTable.NAME, true,
            CityForecastTable.ID to INTEGER + PRIMARY_KEY,
            CityForecastTable.CITY to TEXT,
            CityForecastTable.COUNTRY to TEXT)

    db.createTable(DayForecastTable.NAME, true,
            DayForecastTable.ID to INTEGER + PRIMARY_KEY + AUTOINCREMENT,
            DayForecastTable.DATE to INTEGER,
            DayForecastTable.DESCRIPTION to TEXT,
            DayForecastTable.HIGH to INTEGER,
            DayForecastTable.LOW to INTEGER,
            DayForecastTable.ICON_URL to TEXT,
            DayForecastTable.CITY_ID to INTEGER)
}

我们有一个相似的函数用于删除表。onUpgrade将只是删除表,然后重建它们。我们只是把我们数据库作为一个缓存,所以这是一个简单安全的方法保证我们的表会如我们所期望的那样被重建。如果我有很重要的数据需要保留,我们就需要优化onUpgrade的代码,让它根据数据库版本来做相应的数据转移。

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    db.dropTable(CityForecastTable.NAME, true)
    db.dropTable(DayForecastTable.NAME, true)
    onCreate(db)
}