Android - Android Architecture Components - Room 在 Kotlin 中使用 (1)

参考文章

Android Room 官方文档

Room Persistence Library(官网文档翻译)

在kotlin中使用room(Room Persistence Library)和遇到的坑

Android官方ORM框架ROOM(Google I/O 2017)

啰嗦

Room 不了解的可以看上面文章就可以了,我就不做过多赘述。每个例子都有一个环境或场景,好吧,场景如下:

描述

用户拥有哪些书

User

字段 说明
u_id 序号
u_name 姓名
u_phone 电话
u_create_date 创建日期
u_update_date 更新日期
u_status 状态

Book

字段 说明
b_id 序号
b_name 书名
b_author 作者
b_copyright 出版
b_price 价格
b_status 状态
user_id 用户id

1. 环境配置

kotlin 环境不再赘述,自行操作,或者直接使用 Android Studio 3.0 新建kotlin 工程。

如果是 3.0 建的kotlin 项目, 则 project/build.gradle 下自己带有 google maven 仓库。

allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
    }
}

如果是 2.x 配置的 kotlin 环境,则在 project/build.gradle 下配置google maven 仓库:

allprojects {
    repositories {
        jcenter()
        maven { url 'https://maven.google.com' }
    }
}

参考(墙):https://developer.android.com/topic/libraries/architecture/adding-components.html

配置 Room 依赖

如果是 3.0 推荐使用下面方式

implementation 'android.arch.persistence.room:runtime:1.0.0-alpha5'
// kotlin
kapt "android.arch.persistence.room:compiler:1.0.0-alpha5"
// java
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha5"
// 测试 room
testCompile 'android.arch.persistence.room:testing:1.0.0-alpha5'
// 配合 rxjava2 使用
implementation 'android.arch.persistence.room:rxjava2:1.0.0-alpha5'

如果 是 2.x ,将 implementation 换成 compile 即可,注意:

如果是 kotlin 环境,使用 kapt 来操作注解:

kapt "android.arch.persistence.room:compiler:1.0.0-alpha5"

如果是 java 环境,使用 annotationProcessor 来操作注解:

annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha5"

当然,同时放置也没事。

2. 基本使用

先看看要实现的内容,

Android - Android Architecture Components - Room 在 Kotlin 中使用 (1)_第1张图片

  • entity ( table )
  • dao
  • database
  • converters
  • Dbhelper

2.1 table 实现

table (entity) 实现时,注意实现其构造函数,否则报错;有些字段不支持,通过 TypeConverters 处理。

注解说明:

  • @Entity 指明表,默认表名为实体类名,也可通过 tableName 指定表名
  • @PrimaryKey 主键,自增的话指明 autoGeneratetrue
  • @ColumnInfo 指定列,默认列表为属性名,也可通过name 指定列名

UserTable

import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey
import java.util.*

/**
 * Created by yuan on 02/08/2017.
 * 用户表
 * id 自增
 */
@Entity(tableName = "user")
data class UserTable constructor(@ColumnInfo(name = "u_id")
                                 @PrimaryKey(autoGenerate = true)
                                 var id: Int = 0,
                                 @ColumnInfo(name = "u_name")
                                 var name: String? = null,
                                 @ColumnInfo(name = "u_phone")
                                 var phone: String? = null,
                                 @ColumnInfo(name = "u_create_date")
                                 var createDate: Date? = null,
                                 @ColumnInfo(name = "u_update_date")
                                 var updateDate: Date? = null,
                                 @ColumnInfo(name = "u_status")
                                 var status: Int? = 0
) {
    constructor() : this(0)
}

BookTable

外键可通过 foreignKeys 进行指定,foreignkeys 为数组使用 arrayOf 操作,如下所示:

import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.ForeignKey
import android.arch.persistence.room.PrimaryKey

/**
 * Created by yuan on 02/08/2017.
 * book table
 * 注意:数据类型必须为其支持的类型
 */
@Entity(tableName = "book", foreignKeys = arrayOf(ForeignKey(entity = UserTable::class,
        parentColumns = arrayOf("u_id"),
        childColumns = arrayOf("user_id")))
)
data class BookTable(
        @ColumnInfo(name = "b_id")
        @PrimaryKey(autoGenerate = true)
        var id: Int = 0,
        @ColumnInfo(name = "b_name")
        var name: String = "",
        @ColumnInfo(name = "b_author")
        var author: String = "",
        @ColumnInfo(name = "b_price")
        var price: Double = 0.toDouble(),
        @ColumnInfo(name = "b_copyright")
        var copyright: String = "",
        @ColumnInfo(name = "b_status")
        var status: Int = 0,
        @ColumnInfo(name = "user_id")
        var userId: Int = 0
) {
    // 必须有公共构造方法
    constructor() : this(0)
}

2.2 Dao 实现

Dao 是一个接口

注解说明:

  • @Dao 指明为 Dao
  • @Insert 插入操作
  • @Update 更新操作
  • @Delete 删除操作
  • @Query 查询操作 ,需要指定 Sql 语句

更多操作,下篇会出个实例讲解

UserDao

import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import cn.labelnet.android.roomdb.base.data.tables.UserTable

/**
 * Created by yuan on 02/08/2017.
 * 用户 dao
 */
@Dao
interface UserDao {

    /**
     * 插入一个用户
     */
    @Insert
    fun insertUser(user: UserTable)

    /**
     * 插入多个用户
     */


    /**
     * 查询全部用户
     */
    @Query("select * from user")
    fun selectUsers(): List
}

BookDao

import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import cn.labelnet.android.roomdb.base.data.tables.BookTable

/**
 * Created by yuan on 02/08/2017.
 * book dao
 */
@Dao
interface BookDao {

    @Insert
    fun insertBook(book: BookTable)

    @Query("select * from book")
    fun selectBooks(): List
}

2.3 Database

抽象类,需要继承 RoomDatabase

注解说明

  • @Database 指定操作,将 Dao 层全部集中在一起,可以指定 version 使用 entities 指定所用的 entity(table)
  • @TypeConverters 指定转换器,看要加在什么上面
import android.arch.persistence.room.Database
import android.arch.persistence.room.RoomDatabase
import android.arch.persistence.room.TypeConverters
import cn.labelnet.android.roomdb.base.data.Converters
import cn.labelnet.android.roomdb.base.data.dao.BookDao
import cn.labelnet.android.roomdb.base.data.dao.UserDao
import cn.labelnet.android.roomdb.base.data.tables.BookTable
import cn.labelnet.android.roomdb.base.data.tables.UserTable

/**
 * Created by yuan on 02/08/2017.
 * 操作用户的 database
 */
@Database(entities = arrayOf(UserTable::class, BookTable::class), version = 1)
@TypeConverters(Converters::class)
abstract class AppDataBase : RoomDatabase() {

    /**
     *  user 操作 dao
     */
    abstract fun userDao(): UserDao

    /**
     * book 操作 dao
     */
    abstract fun bookDao(): BookDao

}

2.4 converters

有些类型不可以直接操作,就需要 converter 进行转换,比如日期类型。

上面 UserTable 中的 createDateDate 类型,在生成数据表的存储的时候用 String 进行存储,这是使用 converter 。 当然 Long 类型存储日期时间也是可以的,不过就不是下面写法了,自己另行写吧。

  @ColumnInfo(name = "u_create_date")
  var createDate: Date? = null,

注解说明:

  • @TypeConverter 指明转换方法
  • @TypeConverters 作用于需要转换的地方

如果作用于 Datebase 上,那么整个数据库中日期类型都按照这样的格式转换。

如果作用于 Entity(table) 上,那么就只有该 Entity(table) 转换。

import android.arch.persistence.room.TypeConverter
import java.text.SimpleDateFormat
import java.util.*

/**
 * Created by yuan on 02/08/2017.
 * 转换器
 */
class Converters {

    val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA)

    /**
     * 时间戳转日期
     */
    @TypeConverter
    fun fromTimestamp(value: String?): Date {
        if (value == null) {
            return Date(System.currentTimeMillis())
        }
        synchronized(format) {
            return format.parse(value)
        }
    }

    /**
     * 日期转时间
     */
    @TypeConverter
    fun dateToTimestamp(date: Date?): String {
        if (date == null) {
            val nowDate = Date(System.currentTimeMillis())
            synchronized(format) {
                return format.format(nowDate)
            }
        }
        synchronized(format) {
            return format.format(date)
        }
    }
}

转换器 存储结果:

Android - Android Architecture Components - Room 在 Kotlin 中使用 (1)_第2张图片


2.5 DbHelper

使用单例方式就行初始化db , 生成数据库操作对象:

 val appDb = Room.databaseBuilder(context,
                AppDataBase::class.java,
                DB_NAME)
                .build()

关闭数据库

appDb.close()

数据库版本更新

 val appDb = Room.databaseBuilder(context,
                AppDataBase::class.java,
                DB_NAME)
                .addMigrations(//实现 Migration )
                .build()

完整代码

import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.room.Room
import android.arch.persistence.room.migration.Migration
import android.content.Context
import android.util.Log
import cn.labelnet.android.roomdb.base.data.dao.BookDao
import cn.labelnet.android.roomdb.base.data.dao.UserDao
import cn.labelnet.android.roomdb.base.data.database.AppDataBase

/**
 * Created by yuan on 02/08/2017.
 * db 操作类
 */
class DbHelper constructor(context: Context) {

    var appDb: AppDataBase? = null
    val DB_NAME = "room_test_db.db"

    /**
     * 初始化 db
     */
    init {
        appDb = Room.databaseBuilder(context,
                AppDataBase::class.java,
                DB_NAME)
                .build()
                //.addMigrations(MigrateDb(1, 2))
    }

    /**
     * 获取 user dao
     */
    fun getUserDao(): UserDao {
        return appDb!!.userDao()
    }

    /**
     * 获取 book dao
     */
    fun getBookDao(): BookDao {
        return appDb!!.bookDao()
    }


    /**
     * 单例实现
     */
    companion object {

        var INSTANCE: DbHelper? = null

        fun init(context: Context): DbHelper {
            if (INSTANCE == null) {
                synchronized(DbHelper::class) {
                    if (INSTANCE == null) {
                        INSTANCE = DbHelper(context)
                    }
                }
            }
            return INSTANCE!!
        }

    }

    /**
     * 关闭数据库
     */
    fun onDestory() {
        if (appDb != null) {
            appDb!!.close()
        }
    }

    /**
     * 版本合并
     */
    class MigrateDb(startVersion: Int, endVersion: Int) : Migration(startVersion, endVersion) {
        override fun migrate(database: SupportSQLiteDatabase?) {
            Log.v("DbHelper", "migrate")
        }
    }

}

3. 问题与坑

Question 1

Caused by: java.lang.RuntimeException: cannot find implementation for  AppDatabase_Impl does not exist
                                                       at android.arch.persistence.room.Room.getGeneratedImplementation(Room.java:90)
                                                       at android.arch.persistence.room.RoomDatabase$Builder.build(RoomDatabase.java:340)
                                                       at com.ttp.kotlin.kotlinsample.room.AppDatabase$Companion.getInMemoryDatabase(AppDatabase.kt:19)

解决: kotlin 下使用 kapt

annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha5"

换成

kapt "android.arch.persistence.room:compiler:1.0.0-alpha5"

Question 2

Error:Entities and Pojos must have a usable public constructor.

解决

entity 需要实现构造函数

@Entity(tableName = "user")
data class UserTable constructor(@ColumnInfo(name = "u_id")
                                 @PrimaryKey(autoGenerate = true)
                                 var id: Int = 0,
                                 @ColumnInfo(name = "u_name")
                                 var name: String? = null,
                                 @ColumnInfo(name = "u_phone")
                                 var phone: String? = null,
                                 @ColumnInfo(name = "u_create_date")
                                 var createDate: Date? = null,
                                 @ColumnInfo(name = "u_update_date")
                                 var updateDate: Date? = null,
                                 @ColumnInfo(name = "u_status")
                                 var status: Int? = 0
) {
    // 实现构造函数
    constructor() : this(0)
}

Question 3

Warning:warning: Supported source version 'RELEASE_7' from annotation processor 'android.arch.persistence.room.RoomProcessor' less than -source '1.8'

Warning:warning: Supported source version 'RELEASE_7' from annotation processor 'android.arch.lifecycle.LifecycleProcessor' less than -source '1.8'

解决:使用 kapt 时,不需要重复在build.gradle 上进行引入 kotlin-kapt , 当然没有引入最好了。

apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
// 不需要它,已经默认有了,它导致上面
//apply plugin: 'kotlin-kapt'

Question 4

Error:Cannot figure out how to save this field into database. You can consider adding a type converter for it.
Error:Cannot figure out how to read this field from a cursor.

解决:有些字段或类型不认识,需要在 converter 进行转换;

  • Date
  • ArrayList
class Converters {

    val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA)

    /**
     * 时间戳转日期
     */
    @TypeConverter
    fun fromTimestamp(value: String?): Date {
        if (value == null) {
            return Date(System.currentTimeMillis())
        }
        synchronized(format) {
            return format.parse(value)
        }
    }

    /**
     * 日期转时间
     */
    @TypeConverter
    fun dateToTimestamp(date: Date?): String {
        if (date == null) {
            val nowDate = Date(System.currentTimeMillis())
            synchronized(format) {
                return format.format(nowDate)
            }
        }
        synchronized(format) {
            return format.format(date)
        }
    }
}

Question 5

 Process: cn.labelnet.android.roomdb, PID: 22630
                                                                            java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
                                                                                at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:143)
                                                                                at android.arch.persistence.room.RoomDatabase.beginTransaction(RoomDatabase.java:190)
                                                                                at cn.labelnet.android.roomdb.base.data.dao.UserDao_Impl.insertUser(UserDao_Impl.java:63)
                                                                                at cn.labelnet.android.roomdb.main.MainActivity.onItemClick(MainActivity.kt:53)
                                                                                at cn.labelnet.android.roomdb.main.adapter.MainAdapter$onBindViewHolder$1.onClick(MainAdapter.kt:53)
                                                                                at android.view.View.performClick(View.java:5637)
                                                                                at android.view.View$PerformClick.run(View.java:22429)
                                                                                at android.os.Handler.handleCallback(Handler.java:751)
                                                                                at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                                at android.os.Looper.loop(Looper.java:154)
                                                                                at android.app.ActivityThread.main(ActivityThread.java:6119)
                                                                                at java.lang.reflect.Method.invoke(Native Method)
                                                                                at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
                                                                                at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

解决 : 数据库操作放在 io 线程,不要在 ui 线程做。子线程操作自行解决,我这里就不赘述了。

Question 6

java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
                                                                                at android.arch.persistence.room.RoomOpenHelper.checkIdentity(RoomOpenHelper.java:112)
                                                                                at android.arch.persistence.room.RoomOpenHelper.onOpen(RoomOpenHelper.java:93)
                                                                                at android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper$1.onOpen(FrameworkSQLiteOpenHelper.java:64)
                                                                                at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:266)
                                                                                at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:163)

解决:数据库改变,需要使用版本更新操作。

Room.databaseBuilder(context,
                AppDataBase::class.java,
                DB_NAME)
                .addMigrations(MigrateDb(1, 2))
                .build()

/**
     * 版本合并
     */
    class MigrateDb(startVersion: Int, endVersion: Int) : Migration(startVersion, endVersion) {
        override fun migrate(database: SupportSQLiteDatabase?) {
            // 版本更新操作
        }
    }

4. 最后

以开源,仓库地址 : RoomDb

下篇说明说用

你可能感兴趣的:(android开发经验)