四篇文章带你快速入门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()
升级模式
- 修改版本号。
- 定义升级版本的sql语句。
- 执行语句。
示例
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
- version = 2
- User::class, Book::class
- abstract fun bookDao(): BookDao
- 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
- version = 3
- MIGRATION_2_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")
}
}
}
网友评论