Jetpack 之 Room 小白入手

声明 : https://www.jianshu.com/p/714062a9af75
目录

简介
原理
使用方法

1,基本使用
2, Room 与 LiveData,ViewModel 的结合使用
3,数据库升级

🍀 简介:

   Android 存储数据的方式:SharePerference,文件存储,SQLite 数据库存储,第三方数据库.Google 在 SQLite 基础上封装了了轻量级的数据库 Room,访问数据库更稳更快.
   Room持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同事,获享更强健的数据库访问机制,该库可以帮助您运行应用的设备上创建应用数据的缓存.此缓存应充当应用的单一可信来源,使用户能够在应用中查看关键的信息的一致副本,无论用户是否具有互联网链接.

🍀 原理:

image.png
  • Data Access Objects : 数据访问集
  • Get Entities from db : 从数据库获取实体类
  • Persist changes back to db : 数据持久化后返回数据库
  • get/set field values : 获取字段的值
  • Dao : 包含用户访问数据库的方法

🍀 几个关键的注解:

  • 🐥 @Entity 对应数据库中的一张表,可以设置表名,若不指定,则为类名
  • 🐥 @PrimaryKey 指定表的主键
  • 🐥 @ColumnInfo 指定列的名字,若不指定,则为属性名字
  • 🐥 @Ignore 忽略字段或者方法
  • 🐥 @Dao (data access object) 接口文件或者抽象类,以便对 Entity 进行访问
  • 🐥 @Database 告诉系统这是 Room 数据库对象.enterties 属性用于指定有哪些表,多张表,用逗号隔开,version 用于指定数据库的版本号.

🍀 使用方法:

官网连接 : https://developer.android.google.cn/topic/libraries/architecture/room 查看引用的最新版本

1.build.gradle

   apply plugin: 'kotlin-kapt'
   .......

    //room
    implementation "androidx.room:room-runtime:2.3.0"
    annotationProcessor "androidx.room:room-compiler:2.3.0"
    //Room的Kotlin扩展和对协程的支持
    kapt "androidx.room:room-compiler:2.3.0"

因为我用 kotlin 写的项目,所以要加第三个引用~~~否则会报错哦.
java ,不需要~~

2.创建一个学生的 Entity,即创建一张学生的表

@Entity(tableName = "table_student")
data class Student(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "s_id", typeAffinity = ColumnInfo.INTEGER)
    var id: Int,
    @ColumnInfo(name = "s_name", typeAffinity = ColumnInfo.TEXT)
    var name: String,
    @ColumnInfo(name = "s_sex", typeAffinity = ColumnInfo.TEXT)
    var sex: String
) {

    /***
     * 由于 Room 只能识别和使用一个构造器,如果想使用多个构造器,使用 @Ignore 标签,忽略构造器
     *
     */
    @Ignore
    constructor(name: String, sex: String) : this(0, name, sex) {
        this.name = name
        this.sex = sex
    }
}

3.学生的Dao 接口文件

@Dao
interface StudentDao {
    @Insert
    fun insertSudent(stdent: Student)

    @Delete
    fun deleteStudent(stdent: Student): Int

    @Update
    fun updataStudent(stdent: Student): Int

    @Query("select * from table_student")
    fun getStudentList(): MutableList<Student>

    @Query("select * from table_student where s_id =:id")
    fun getStudent(id: Int): Student
}

4.创建数据库

@Database(entities = [Student::class], version = 1)
abstract class StuentDataBase : RoomDatabase() {
    companion object {
        @Synchronized
        fun getInstance(context: Context): StuentDataBase {
            var stuentDataBase: StuentDataBase? = null
            val DATABASE_NAME = "student_db"
            stuentDataBase =
                Room.databaseBuilder(context, StuentDataBase::class.java, DATABASE_NAME).build()
            return stuentDataBase
        }
    }

    abstract fun studentDao(): StudentDao
}

5.对数据库进行增删改查

⚠️⚠️⚠️不能再 UI线程操作增删改查,必须在工作线程中⚠️⚠️⚠️
看完成的代码吧,我是通过 AsycTask 的方式实现的,当然了,其他方式还有很多种~~~

class TestActivity : AppCompatActivity() {
    lateinit var stuentDataBase: StuentDataBase
    lateinit var studentList: MutableList<Student>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        stuentDataBase = StuentDataBase.getInstance(this)

    }

    fun onclick(view: View) {
        when (view.id) {
            R.id.button1 -> {//插入数据
                InsertStudentTask(Student("张三", "男")).execute()
            }
            R.id.button -> {//删除数据
                DeleteStudentTask(Student(2, "张三", "男")).execute()
            }
            R.id.button2 -> {//更新数据
                UpdateStudentTask(Student(1, "张三", "女")).execute()
            }
            R.id.button3 -> {//查询数据
                QueryStudentTask().execute()
            }
        }
    }

    inner class InsertStudentTask(var student: Student) : AsyncTask<Void?, Void?, Void?>() {

        override fun doInBackground(vararg p0: Void?): Void? {
            stuentDataBase.studentDao().insertSudent(student)
            return null
        }

        override fun onPostExecute(unused: Void?) {
            super.onPostExecute(unused)
        }


    }

    inner class DeleteStudentTask(var student: Student) : AsyncTask<Void?, Void?, Void?>() {

        override fun doInBackground(vararg p0: Void?): Void? {
            var deleteStudent = stuentDataBase.studentDao().deleteStudent(student)
            Log.e("TestActivity", "删除成功$deleteStudent")
            return null
        }

        override fun onPostExecute(unused: Void?) {
            super.onPostExecute(unused)
        }


    }

    inner class UpdateStudentTask(var student: Student) : AsyncTask<Void?, Void?, Void?>() {

        override fun doInBackground(vararg p0: Void?): Void? {
            var updataStudent = stuentDataBase.studentDao().updataStudent(student)
            Log.e("TestActivity", "更新成功$updataStudent")

            return null
        }

        override fun onPostExecute(unused: Void?) {
            super.onPostExecute(unused)
        }


    }

    inner class QueryStudentTask : AsyncTask<Void?, Void?, Void?>() {

        override fun doInBackground(vararg p0: Void?): Void? {
            studentList = stuentDataBase.studentDao().getStudentList()
            return null
        }

        override fun onPostExecute(unused: Void?) {
            super.onPostExecute(unused)
            for (student in studentList) {
                Log.e("TestActivity", student.toString())
            }
        }
        
    }
    
}

看运行截图


image.png

好了,到此为止,简单的使用已经完成了~~~~😊😊😊

🍀 Room 与 LiveData,ViewModel 的结合使用

   我们知道LiveData 通常和 ViewModel 一起使用.ViewModel 是存放数据的,因此我们可以将数据库的初始化放在 Viewm 中.当我们执行增,删,改的操作的时候,我们可以用过 LiveData 组件通知我们数据的变化,实现数据自动更新.

1,修改StudentDao

    @Query("select * from table_student")
    fun getStudentList(): LiveData<MutableList<Student>>

2,创建 StudentViewModel

   数据库的初始化需要 Context,我们不宜传入 Context,会导致内存泄漏,但是我们可以使用子类AndroidViewModel,其构造器中含有 Application 的参数,Application 作为 Context 的子类,可以用于数据库的初始化.

public class StudentViewModel extends AndroidViewModel {

    private StuentDataBase stuentDataBase;

    public LiveData<List<Student>> getLivaDataStudent() {

        return livaDataStudent;
    }

    private LiveData<List<Student>> livaDataStudent;

    public StudentViewModel(@NonNull @NotNull Application application) {
        super(application);
        stuentDataBase = StuentDataBase.Companion.getInstance(application);
        livaDataStudent = stuentDataBase.studentDao().getStudentList();
    }

}

3,在TestActivity实例化 StudentViewModel,监听数据变化

class TestActivity : AppCompatActivity() {
    lateinit var stuentDataBase: StuentDataBase
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        stuentDataBase = StuentDataBase.getInstance(this)
        var studentViewModel = ViewModelProvider(this).get(StudentViewModel::class.java)
        studentViewModel.livaDataStudent.observe(this,
            {
                for (student in it) {
                    Log.e("TestActivity", student.toString())
                }
            })
    }
}

当在执行插入,修改,删除的操作的时候,会自动执行查询才做,检测结果.

🍀 数据库升级

  随着业务的增加,我们会对数据库做一些调整,比如在表中新加一个字段.Android 提供了一个类为 Migration 的类来进行升级.Migration有两个参数,startVersion 和 endVersion.

//从 1 到 2
  val MIGRATION_1_2: Migration = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            //执行相关的操作
        }
    }
 //从 2 到 3
  val MIGRATION_2_3: Migration = object : Migration(2, 3) {
        override fun migrate(database: SupportSQLiteDatabase) {
            //执行相关的操作
        }
    }
//.........

以此类推,然后通过addMigrations()添加升级,

Room.databaseBuilder(context.applicationContext,StuentDataBase::class.java,DATABASE_NAME)
                    .addMigrations(MIGRATION_1_2,MIGRATION_2_3,....)
                    .build()

同事,也要修改数据库的版本号

@Database(entities = [Student::class], version = 2)

⚠️如果当前数据库版本的为 1,而当前要安装的版本为 3,那么 Room 会判断当前有没有从 1 升到 3 的方案,如果有,则执行 Migration(1 , 3) 的方案,如果没有,则会执行 Migration(1 , 2),再从Migration(2 , 3).
⚠️如果数据库版本升到 4,但又没有相应 Migration ,为了防止异常崩溃,我们可以再创建数据库的时候,加入fallbackToDestructiveMigration()方法,该方法在出现异常的时候,重新创建数据库表,虽然不会崩溃,但是数据会丢失哦.

Room.databaseBuilder(context.applicationContext,StuentDataBase::class.java,DATABASE_NAME)
                    .fallbackToDestructiveMigration()
                    .addMigrations(MIGRATION_1_2,MIGRATION_2_3,....)
                    .build()

举例说明: 上面的例子中,学生的表中,新加一个 age 字段

1.Student

@Entity(tableName = "table_student")
data class Student(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "s_id", typeAffinity = ColumnInfo.INTEGER)
    var id: Int,
    @ColumnInfo(name = "s_name", typeAffinity = ColumnInfo.TEXT)
    var name: String,
    @ColumnInfo(name = "s_sex", typeAffinity = ColumnInfo.TEXT)
    var sex: String,
    @ColumnInfo(name = "s_age")
    var age: Int,
) {

    /***
     * 由于 Room 只能识别和使用一个构造器,如果想使用多个构造器,使用 @Ignore 标签,忽略构造器
     *
     */
    @Ignore
    constructor(name: String, sex: String) : this(0, name, sex, 0) {
        this.name = name
        this.sex = sex
    }

}

2 编写Migration类,并执行升级

@Database(entities = [Student::class], version = 2)
abstract class StuentDataBase : RoomDatabase() {
    companion object {
        private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                //创建一个新表
                database.execSQL(
                    "create table table_student2(s_id INTEGER PRIMARY KEY not null,s_name TEXT not null,s_sex TEXT not null,s_age  INTEGER not null default 0 )"
                )
                //复制数据
                database.execSQL("insert into table_student2(s_id,s_name,s_sex) select s_id,s_name,s_sex from table_student")
                //删除旧的表
                database.execSQL("drop table table_student")
                //重新命名数据表的名字
                database.execSQL("alter table table_student2 rename to table_student")
            }
        }
        private var stuentDataBase: StuentDataBase? = null
        private const val DATABASE_NAME = "student_db"

        @Synchronized
        fun getInstance(context: Context): StuentDataBase {
            if (stuentDataBase == null) {
                stuentDataBase = Room.databaseBuilder(
                    context.applicationContext,
                    StuentDataBase::class.java,
                    DATABASE_NAME
                )
                    .addMigrations(MIGRATION_1_2)
                    .build()
            }
            return stuentDataBase as StuentDataBase
        }
    }

    abstract fun studentDao(): StudentDao
}

3,TestActivity还是不变的~,看一下运行截图

我们更新了一条数据,更新是没问题的,age 字段也打印出来啦,默认是 0.


image.png

END

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容