四篇文章带你快速入门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

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

    @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

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

    @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
}

修改后的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")
        }
    }
}

项目代码
项目视频

你可能感兴趣的:(四篇文章带你快速入门Jetpck(下)之Room,WorkManager)