四篇文章带你快速入门Jetpck(下)之Room,WorkManager

四篇文章带你快速入门Jetpck(下)之Room,WorkManager

Jetpack

Jetpack 是一个由多个库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者精力集中编写重要的代码。

Android Architecture Component (AAC)

image.png

官方推荐架构

img

请注意,每个组件仅依赖于其下一级的组件。例如,Activity 和 Fragment 仅依赖于视图模型。存储区是唯一依赖于其他多个类的类;在本例中,存储区依赖于持久性数据模型和远程后端数据源。

Room

ORM:也叫对象关系映射。

将面相对象的语言和面相关系的数据库之间建立一种映射关系,称之为了ORM。

ORM框架的好处就是,赋予我们可以用面相对象的思维来和数据库进行交互,绝大多数情况不用在和SQL语句打交道。

Android推出的ORM框架,将它加入了Jetpack中,这就是我们将学习的Room。

Room结构

由Entity,Dao和Database三部分组成。

  • Entity:封装实际数据的实体类,每个实体类都会在数据中对应一张表,并且表中的列是根据实体类中的字段自动生成的。
  • Dao:Dao是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编程时,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。
  • Database:用于定义数据库中的关键信息,包括数据库的版本号,包含哪些实体类以及提供Dao层的访问实例。

添加依赖

apply plugin: 'kotlin-kapt'
implementation 'androidx.room:room-runtime:2.2.5'
annotationProcessor "androidx.room:room-compiler:2.2.5"
kapt "androidx.room:room-compiler:2.2.5"

定义Entity

首先定义一个Entity,也就是实体类。

@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {

    @PrimaryKey(autoGenerate = true)
    var id: Long = 0

}

这里我们在User的类名上使用@Entity注解,将它声明成了一个实体类,然后在User类中添加了一个id字段,并使用@PrimaryKey注解将它设为了主键,再把autoGenerate参数指定成true,使得主键的值是自动生成的。

定义Dao

@Dao
interface UserDao {

    @Insert
    fun insertUser(user: User): Long

    @Update
    fun updateUser(newUser: User)

    @Query("select * from User")
    fun loadAllUsers(): List<User>

    @Query("select * from User where age > :age")
    fun loadUsersOlderThan(age: Int): List<User>

    @Delete
    fun deleteUser(user: User)

    @Query("delete from User where lastName = :lastName")
    fun deleteUserByLastName(lastName: String): Int

}

定义Database

@Database(version = 1, entities = [User::class])
abstract class AppDatabase : RoomDatabase() {

    abstract fun userDao(): UserDao

    companion object {

        private var instance: AppDatabase? = null

        @Synchronized
        fun getDatabase(context: Context): AppDatabase {
            instance?.let {
                return it
            }
            return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").build().apply {
                instance = this
            }
        }
    }

}

Room的数据库升级

数据库接口不可能在设计好了之后就永远一成不变,随着需求和版本的变更,数据库也是需要升级的。

简单模式

fallbackToDestructiveMigration会将当前的数据库销毁,然后在重新创建,随之而来的问题就是数据全部丢失,适合测试阶段。

 Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").fallbackToDestructiveMigration().build()

升级模式

  1. 修改版本号。
  2. 定义升级版本的sql语句。
  3. 执行语句。

示例

User

/**
 * @Entity 将类声明成实体类
 */
@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {
    /**
     * @PrimaryKey 注解将字段设置为主键
     * autoGenerate 为true 为主键的值为自动生成
     */
    @PrimaryKey(autoGenerate = true)
    var id: Long = 0
}

UserDao

/**
 * @Dao 注解才能识别他是Dao
 * UserDao的内部就是根据业务需求对各种数据库操作进行的封装。
 * 一般指的是CRUD操作
 *
 * Room编写SQL语句支持在编译时动态建材SQL语句语法
 */
@Dao
interface UserDao {

    @Insert
    fun insertUser(user: User): Long

    @Update
    fun updateUser(user: User)

    @Query("select * from User")
    fun loadAllUsers(): List<User>

    @Query("select * from User where age > :age")
    fun loadUsersOlderThan(age: Int): List<User>

    @Delete
    fun deleteUser(user: User)

    @Query("delete from User where lastName = :lastName")
    fun deleteUserByLastName(lastName: String): Int
}

AppDatabase

/**
 * @Database 声明了数据库的版本号以及包含哪些实体类,多个是实体类用逗号隔离开来。
 * 另外AppDatabase类必须继承自RoomDatabase类,并且一定要使用abstract关键字声明成抽象类
 */
@Database(version = 1, entities = [User::class])
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao

    companion object {
        private var instance: AppDatabase? = null

        @Synchronized
        fun getDatabase(context: Context): AppDatabase {
            instance?.let {
                return it
            }
            return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").build().apply {
                instance = this
            }
        }
    }
}

RoomActivity

class RoomActivity : AppCompatActivity() {
    val TAG = this.javaClass.simpleName
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_room)
        val userDao = AppDatabase.getDatabase(this).userDao()
        val user1 = User("Y", "X", 22)
        val user2 = User("T", "Y", 33)
        btn_add.setOnClickListener {
            thread {
                user1.id = userDao.insertUser(user1)
                user2.id = userDao.insertUser(user2)
            }
        }
        btn_udpdate.setOnClickListener {
            thread {
                user1.age = 50
                userDao.updateUser(user1)
            }
        }
        btn_delete.setOnClickListener {
            thread {
                userDao.deleteUserByLastName("Y")
            }
        }
        btn_query.setOnClickListener {
            thread {
                for (user in userDao.loadAllUsers()) {
                    Log.d(TAG, user.toString())
                }
            }
        }
    }
}

Book

@Entity
data class Book(var name: String, var pages: Int) {
    @PrimaryKey(autoGenerate = true)
    var id: Long = 0
}

BookDao

@Dao
interface BookDao {
    @Insert
    fun insertBook(book: Book): Long

    @Query("select * from Book")
    fun loadAllBooks(): List<Book>
}

修改后的appdatabase

  1. version = 2
  2. User::class, Book::class
  3. abstract fun bookDao(): BookDao
  4. addMigrations(MIGRATION_1_2)
//变动:1
@Database(version = 2, entities = [User::class, Book::class])
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
    //变动:2
    abstract fun bookDao(): BookDao

    companion object {
    //变动:3
        val MIGRATION_1_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("create table Book (id integer primary key autoincrement not null,name text not null,pages integer not null)")
            }
        }

        private var instance: AppDatabase? = null

        //变动:4
        @Synchronized
        fun getDatabase(context: Context): AppDatabase {
            instance?.let {
                return it
            }
            return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").addMigrations(MIGRATION_1_2).build().apply {
                instance = this
            }
        }
    }
}

再次修改 增加 var author: String

@Entity
data class Book(var name: String, var pages: Int, var author: String) {
    @PrimaryKey(autoGenerate = true)
    var id: Long = 0
}

再次修改第三版 version =3

  1. version = 3
  2. MIGRATION_2_3
  3. addMigrations(MIGRATION_1_2, MIGRATION_2_3)
@Database(version = 3, entities = [User::class, Book::class])

val MIGRATION_2_3 = object : Migration(1, 2) {
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("alter table Book add column author text not null default 'unknown'")
    }
}

return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database").addMigrations(MIGRATION_1_2, MIGRATION_2_3).build().apply {
                instance = this
            }

WorkManager

WorkManager很适合用于处理一些要求定时执行的任务,它可以根据操作系统的版本自动选择底层是使用AlarmManager实现还是JobScheduler实现,从而降低了我们的使用成本。

另外, WorkManager还支持周期性任务、链式任务处理等功能,是一个非常强大的工具。

添加依赖

implementation 'androidx.work:work-runtime:2.4.0'

WorkManager的基本用法

WorkManager的基本用法其实非常简单,主要分为以下3步:

定义一个后台任务,并实现具体的任务逻辑。

配置该后台任务的运行条件和约束信息,并构建后台任务请求。

将该后台任务请求传入WorkManager的enqueue()方法中,系统会在合适的时间运行。

定义后台任务

第一步要定义一个后台任务,这里创建一个SimpleWorker类,代码如下所示:

class SimpleWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        Log.d("SimpleWorker", "do work in SimpleWorker")
        return Result.success()
    }
}

对后台任务进行配置

第二步,配置后台任务的运行条件和约束信息,代码如下所示:

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
    .setInitialDelay(5, TimeUnit.MINUTES)
    .build()

最后一步,将构建出的后台任务请求传入WorkManager的enqueue()方法中,系统就会在合适的时间去运行了,代码如下所示:

WorkManager.getInstance(context).enqueue(request)

延时启动

setInitialDelay(1, TimeUnit.MINUTES)

设置标签

addTag("example")

取消任务

WorkManager.getInstance(this).cancelWorkById(request.id)
WorkManager.getInstance(this).cancelAllWork()
WorkManager.getInstance(this).cancelAllWorkByTag("example")

观察任务

WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id)
    .observe(this) { workInfo ->
                when (workInfo.state) {
                    WorkInfo.State.SUCCEEDED -> {
                        Log.d(TAG, "WorkInfo.State.SUCCEEDED")
                    }
                    WorkInfo.State.FAILED -> {
                        Log.d(TAG, "WorkInfo.State.FAILED")
                    }
                    WorkInfo.State.RUNNING -> {
                        Log.d(TAG, "WorkInfo.State.RUNNING")
                    }
                    WorkInfo.State.CANCELLED -> {
                        Log.d(TAG, "WorkInfo.State.CANCELLED")
                    }
                    WorkInfo.State.ENQUEUED -> {
                        Log.d(TAG, "WorkInfo.State.ENQUEUED")
                    }
                }
           }

重复执行任务

doWork方法要返回Result.retry().

setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.SECONDS)

示例

class WorkManagerActivity : AppCompatActivity() {
    val TAG = this.javaClass.simpleName
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_work_manager)
        btn_do.setOnClickListener {
            val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
                .setInitialDelay(2, TimeUnit.SECONDS).addTag("example")
                .setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.SECONDS)
                .build()



            WorkManager.getInstance(this).enqueue(request)
            WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id)
                .observe(this) { workInfo ->
                    when (workInfo.state) {
                        WorkInfo.State.SUCCEEDED -> {
                            Log.d(TAG, "WorkInfo.State.SUCCEEDED")
                        }
                        WorkInfo.State.FAILED -> {
                            Log.d(TAG, "WorkInfo.State.FAILED")
                        }
                        WorkInfo.State.RUNNING -> {
                            Log.d(TAG, "WorkInfo.State.RUNNING")
                        }
                        WorkInfo.State.CANCELLED -> {
                            Log.d(TAG, "WorkInfo.State.CANCELLED")
                        }
                        WorkInfo.State.ENQUEUED -> {
                            Log.d(TAG, "WorkInfo.State.ENQUEUED")
                        }
                    }
                }
            //WorkManager.getInstance(this).cancelWorkById(request.id)
        }
        btn_cancel.setOnClickListener {
            //WorkManager.getInstance(this).cancelAllWork()
            WorkManager.getInstance(this).cancelAllWorkByTag("example")
        }
    }
}

项目代码
项目视频

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

推荐阅读更多精彩内容