1、ViewModel
用于将数据与Activity分离,这样在Activity声明周期中,数据不会丢失。
(1)简单使用
implementation ("androidx.lifecycle:lifecycle-extensions:2.2.0") // 使用ViewModel组件需要额外添加
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/infoText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="32sp"/>
<Button
android:id="@+id/plusOneBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Plus One"/>
</LinearLayout>
package com.jpc.jetpackapp
import androidx.lifecycle.ViewModel
// 与MainActivity有关的数据
class MainViewModel: ViewModel() {
var counter = 0;
}
package com.jpc.jetpackapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
private lateinit var infoText: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
infoText = findViewById<TextView>(R.id.infoText)
// ViewModelProvider(<你的Activity或Fragment实例>).get(<你的ViewModel>::class.java)
// 不在onCreate方法中创建ViewModel是因为他的生命周期比Activity长,如果Activity重建就会重新创建ViewModel造成数据丢失
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
val plusOneBtn = findViewById<Button>(R.id.plusOneBtn)
plusOneBtn.setOnClickListener{
viewModel.counter++
refreshCounter()
}
refreshCounter()
}
private fun refreshCounter(){
infoText.text = viewModel.counter.toString()
}
}
(2)传递参数并持久化数据
package com.jpc.jetpackapp
import androidx.lifecycle.ViewModel
class MainViewModel(private val counterReserved: Int): ViewModel() {
var counter = counterReserved; // counterReserved用于记录之前保存的数据
}
package com.jpc.jetpackapp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
/**
* MainViewModelFactory的构造函数中也接收了一个countReserved参数。另外
* ViewModelProvider.Factory接口要求我们必须实现create()方法,因此这里在
* create()方法中我们创建了MainViewModel的实例,并将countReserved参数传了进去。
* 为什么这里就可以创建MainViewModel的实例了呢?因为create()方法的执行时机和
* Activity的生命周期无关,所以不会产生之前提到的问题
*/
class MainViewModelFactory(private val counterReserved: Int): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MainViewModel(counterReserved) as T
}
}
package com.jpc.jetpackapp
import android.content.Context
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.core.content.edit
import androidx.lifecycle.ViewModelProvider
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
private lateinit var infoText: TextView
private lateinit var sp: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
infoText = findViewById<TextView>(R.id.infoText)
// ViewModelProvider(<你的Activity或Fragment实例>).get(<你的ViewModel>::class.java)
// 不在onCreate方法中创建ViewModel是因为他的生命周期比Activity长,如果Activity重建就会重新创建ViewModel造成数据丢失
val plusOneBtn = findViewById<Button>(R.id.plusOneBtn)
sp = getPreferences(Context.MODE_PRIVATE)
val counterReserved = sp.getInt("count_reserved", 0)
// 通过ViewModelFactory创建ViewModel并传递数据
viewModel = ViewModelProvider(this, MainViewModelFactory(counterReserved)).get(MainViewModel::class.java)
plusOneBtn.setOnClickListener{
viewModel.counter++
refreshCounter()
}
val clearBtn = findViewById<Button>(R.id.clearBtn)
clearBtn.setOnClickListener{
viewModel.counter = 0
refreshCounter()
}
refreshCounter()
}
private fun refreshCounter(){
infoText.text = viewModel.counter.toString()
}
override fun onPause() {
super.onPause()
// 保存数据到SharedPreference
sp.edit{
putInt("count_reserved", viewModel.counter)
}
}
}
2、Lifecycles
用于感知Activity的生命周期
package com.jpc.jetpackapp
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
/**
* 有了Lifecycle对象之后,我们就可以在任何地方调用lifecycle.currentState来主动获
* 知当前的生命周期状态。lifecycle.currentState返回的生命周期状态是一个枚举类型,
* 一共有INITIALIZED、DESTROYED、CREATED、STARTED、RESUMED这5种状态类型
*/
class MyObserver(val lifestyle: Lifecycle): LifecycleObserver {
// 使用注解
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun activityStart(){
Log.d("MyObserver", "activityStart")
Log.d("MyObserver", "Activity State = ${lifestyle.currentState}")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun activityStop(){
Log.d("MyObserver", "activityStop")
Log.d("MyObserver", "Activity State = ${lifestyle.currentState}")
}
}
在MainActivity中编写代码
// 传入lifecycle
lifecycle.addObserver(MyObserver(lifecycle))
3、LiveData
是Jetpack提供的一种响应式编程组件,它可以包含任何类型的数据,并在数据发生变化的时候通知给观察者。LiveData特别适合与ViewModel结合在一起使用。
LiveData之所以能够成为Activity与ViewModel之间通信的桥梁,并且还不会有内存泄漏的风险,靠的就是Lifecycles组件。LiveData在内部使用了Lifecycles组件来自我感知生命周期的变化,从而可以在Activity销毁的时候及时释放引用,避免产生内存泄漏的
问题。
另外,由于要减少性能消耗,当Activity处于不可见状态的时候(比如手机息屏,或者被其他的Activity遮挡),如果LiveData中的数据发生了变化,是不会通知给观察者的。只有当Activity重新恢复可见状态时,才会将数据通知给观察者,而LiveData之所以能够实现这种细节的优化,依靠的还是Lifecycles组件。
还有一个小细节,如果在Activity处于不可见状态的时候,LiveData发生了多次数据变化,当Activity恢复可见状态时,只有最新的那份数据才会通知给观察者,前面的数据在这种情况下相
当于已经过期了,会被直接丢弃。
(1)简单使用
package com.jpc.jetpackapp
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
/**
* 将counter变量修改成了一个MutableLiveData对象,并指定它的泛型为Int,表
* 示它包含的是整型数据。MutableLiveData是一种可变的LiveData,它的用法很简单,主要
* 有3种读写数据的方法,分别是getValue()、setValue()和postValue()方法。
* getValue()方法用于获取LiveData中包含的数据;setValue()方法用于给LiveData设置数
* 据,但是只能在主线程中调用;postValue()方法用于在非主线程中给LiveData设置数据。
* 而上述代码其实就是调用getValue()和setValue()方法对应的语法糖写法
*/
class MainViewModel(private val counterReserved: Int): ViewModel() {
// 暴露给外部不可变的LiveData
val counter: LiveData<Int>
get() = _counter
// 可变的LiveData设置为私有
private val _counter = MutableLiveData<Int>(); // counterReserved用于记录之前保存的数据
init {
_counter.value = counterReserved
}
fun plusOne(){
val count = counter.value ?: 0
_counter.value = count + 1
}
fun clear(){
_counter.value = 0
}
}
package com.jpc.jetpackapp
import android.content.Context
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.core.content.edit
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
private lateinit var infoText: TextView
private lateinit var sp: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
infoText = findViewById<TextView>(R.id.infoText)
// ViewModelProvider(<你的Activity或Fragment实例>).get(<你的ViewModel>::class.java)
// 不在onCreate方法中创建ViewModel是因为他的生命周期比Activity长,如果Activity重建就会重新创建ViewModel造成数据丢失
val plusOneBtn = findViewById<Button>(R.id.plusOneBtn)
sp = getPreferences(Context.MODE_PRIVATE)
val counterReserved = sp.getInt("count_reserved", 0)
// 通过ViewModelFactory创建ViewModel并传递数据
viewModel = ViewModelProvider(this, MainViewModelFactory(counterReserved)).get(MainViewModel::class.java)
plusOneBtn.setOnClickListener{
viewModel.plusOne()
}
val clearBtn = findViewById<Button>(R.id.clearBtn)
clearBtn.setOnClickListener{
viewModel.clear()
}
viewModel.counter.observe(this) { count ->
infoText.text = count.toString()
}
// 传入lifecycle
lifecycle.addObserver(MyObserver(lifecycle))
}
override fun onPause() {
super.onPause()
sp.edit{
putInt("count_reserved", viewModel.counter.value ?: 0)
}
}
}
(2)转换数据
map()方法,这个方法的作用是将实际包含数据的LiveData和仅用于观察数据的LiveData进行转换。
package com.jpc.jetpackapp
data class User(var firstName: String, var lastName: String, var age: Int)
package com.jpc.jetpackapp
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.map
/**
* 将counter变量修改成了一个MutableLiveData对象,并指定它的泛型为Int,表
* 示它包含的是整型数据。MutableLiveData是一种可变的LiveData,它的用法很简单,主要
* 有3种读写数据的方法,分别是getValue()、setValue()和postValue()方法。
* getValue()方法用于获取LiveData中包含的数据;setValue()方法用于给LiveData设置数
* 据,但是只能在主线程中调用;postValue()方法用于在非主线程中给LiveData设置数据。
* 而上述代码其实就是调用getValue()和setValue()方法对应的语法糖写法
*/
class MainViewModel(private val counterReserved: Int): ViewModel() {
private val userLiveData = MutableLiveData<User>()
/**
* map()方法接收两个参数:第一个参数是原始的LiveData对象;第二个参数是一个转换函
* 数,我们在转换函数里编写具体的转换逻辑即可。这里的逻辑也很简单,就是将User对象转换
* 成一个只包含用户姓名的字符串。
* 当userLiveData的数据发生变化时,map()方法
* 会监听到变化并执行转换函数中的逻辑,然后再将转换之后的数据通知给userName的观察者
*/
val userName: LiveData<String> = userLiveData.map { user ->
"${user.firstName} ${user.lastName}"
}
}
switchMap方法
package com.jpc.jetpackapp
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
object Repository {
fun getUser(userId: String): LiveData<User> {
val liveData = MutableLiveData<User>()
liveData.value = User(userId, userId, 0)
return liveData
}
fun refresh(): LiveData<User> {
val liveData = MutableLiveData<User>()
liveData.value = User("1", "1", 0)
return liveData
}
}
package com.jpc.jetpackapp
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.map
import androidx.lifecycle.switchMap
class MainViewModel(private val counterReserved: Int): ViewModel() {
/**
* switchMap()方法同样接收两个参数:第一个参数传入我们新增的userIdLiveData,
* switchMap()方法会对它进行观察;第二个参数是一个转换函数,注意,我们必须在这个转换
* 函数中返回一个LiveData对象,因为switchMap()方法的工作原理就是要将转换函数中返回
* 的LiveData对象转换成另一个可观察的LiveData对象。那么很显然,我们只需要在转换函数中
* 调用Repository的getUser()方法来得到LiveData对象,并将它返回就可以了。
* 为了让你能更清晰地理解switchMap()的用法,我们再来梳理一遍它的整体工作流程。首先,
* 当外部调用MainViewModel的getUser()方法来获取用户数据时,并不会发起任何请求或者
* 函数调用,只会将传入的userId值设置到userIdLiveData当中。一旦userIdLiveData的
* 数据发生变化,那么观察userIdLiveData的switchMap()方法就会执行,并且调用我们编
* 写的转换函数。然后在转换函数中调用Repository.getUser()方法获取真正的用户数据。
* 同时,switchMap()方法会将Repository.getUser()方法返回的LiveData对象转换成一
* 个可观察的LiveData对象,对于Activity而言,只要去观察这个LiveData对象就可以了
*/
private val userIdLiveData = MutableLiveData<String>()
val user: LiveData<User> = userIdLiveData.switchMap { userId ->
Repository.getUser(userId)
}
fun getUser(userId: String){
userIdLiveData.value = userId
}
/**
* 我们定义了一个不带参数的refresh()方法,又对应地定义了一个
* refreshLiveData,但是它不需要指定具体包含的数据类型,因此这里我们将LiveData的泛
* 型指定成Any?即可。
* 接下来就是点睛之笔的地方了,在refresh()方法中,我们只是将refreshLiveData原有的
* 数据取出来(默认是空),再重新设置到refreshLiveData当中,这样就能触发一次数据变
* 化。是的,LiveData内部不会判断即将设置的数据和原有数据是否相同,只要调用了
* setValue()或postValue()方法,就一定会触发数据变化事件。
* 然后我们在Activity中观察refreshResult这个LiveData对象即可,这样只要调用了
* refresh()方法,观察者的回调函数中就能够得到最新的数据。
*/
private val refreshLiveData = MutableLiveData<Any?>()
val refreshResult = refreshLiveData.switchMap {
Repository.refresh() // 假设Repository中已经定义了refresh()方法
}
fun refresh() {
refreshLiveData.value = refreshLiveData.value
}
}
val getUserBtn = findViewById<Button>(R.id.getUserBtn)
getUserBtn.setOnClickListener{
val userId = (0..10000).random().toString()
viewModel.getUser(userId)
}
viewModel.user.observe(this){ user ->
infoText.text = user.firstName
}
4、Room
Room用于简化对SQLite的操作,它主要由Entity、Dao和Database这3部分组成。
- Entity。用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并且表中的列是根据实体类中的字段自动生成的。
- Dao。Dao是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。
- Database。用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提 供Dao层的访问实例。
(1)简单使用
首先在最外层的build.gradle.kts文件引入ksp插件
plugins {
id("com.android.application") version "8.2.1" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
id("com.google.devtools.ksp") version "1.9.22-1.0.17" apply false // 使用ksp
}
在app下的build.gradle.kts添加
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp") // 使用 KSP 替代 kapt 可以显著提高性能
}
// 使用Room
val room_version = "2.6.1"
implementation("androidx.room:room-runtime:$room_version")
ksp("androidx.room:room-compiler:$room_version")
// room针对kotlin协程功能的扩展库
implementation("androidx.room:room-ktx:$room_version")
编写Entity
package com.jpc.jetpackapp
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity // 表示这是一个实体类
data class User(var firstName: String, var lastName: String, var age: Int){
@PrimaryKey(autoGenerate = true) // 标识为主键并自增
var id: Long = 0
}
编写Dao
package com.jpc.jetpackapp
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
@Dao // 表示为Dao层
interface UserDao {
@Insert
fun insertUser(user: User): Long
@Update
fun updateUser(user: User)
@Query("select * from user") // 具体的SQL语句就要使用Query注解
fun getAllUser(): List<User>
@Query("select * from user where age > :age")
fun getUserByAge(age: Int): List<User>
@Delete
fun deleteUser(user: User)
@Query("delete from user where lastName = :lastName")
fun deleteUserByLastName(lastName: String)
}
在MainActivity中编写
val addDataBtn = findViewById<Button>(R.id.addDataBtn)
val updateDataBtn = findViewById<Button>(R.id.updateDataBtn)
val queryDataBtn = findViewById<Button>(R.id.queryDataBtn)
val deleteDataBtn = findViewById<Button>(R.id.deleteDataBtn)
val userDao = AppDatabase.getDatabase(this).userDao()
val user1 = User("张", "三", 21)
val user2 = User("张", "五", 20)
/**
* 由于数据库操作属于耗时操作,Room默认是不允许在主线程中进行数据库操作的,因此
* 上述代码中我们将增删改查的功能都放到了子线程中
*/
addDataBtn.setOnClickListener {
thread {
val id1 = userDao.insertUser(user1)
val id2 = userDao.insertUser(user2)
}
}
updateDataBtn.setOnClickListener {
thread {
user1.age = 40
userDao.updateUser(user1)
}
}
deleteDataBtn.setOnClickListener {
thread {
userDao.deleteUserByLastName("五")
}
}
queryDataBtn.setOnClickListener {
thread {
for (user in userDao.getAllUser()) {
Log.d("UserDao", user.toString())
}
}
}
(2)数据库升级
添加表
@Entity
data class Book(var name: String, var pages: Int) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
package com.jpc.jetpackapp;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
@Dao
interface BookDao {
@Insert
fun insertBook(book: Book): Long
@Query("select * from Book")
fun loadAllBooks(): List<Book>
}
package com.jpc.jetpackapp
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
// 声明数据库版本号及包括哪些实体,多个实体使用逗号隔开
@Database(version = 2, entities = [User::class, Book::class])
abstract class AppDatabase: RoomDatabase() {
// 只需要声明一个抽象方法,用于获取Dao的实例,不需要实现具体逻辑,由Room底层实现了
abstract fun userDao(): UserDao
abstract fun bookDao(): BookDao
companion object{
/**
* 在companion object结构体中,我们实现了一个Migration的
* 匿名类,并传入了1和 2这两个参数,表示当数据库版本从1升级到2的时候就执行这个匿名类中
* 的升级逻辑。匿名类实例的变量命名也比较有讲究,这里命名成MIGRATION_1_2,可读性更
* 高。由于我们要新增一张Book表,所以需要在migrate()方法中编写相应的建表语句。另外必
* 须注意的是,Book表的建表语句必须和Book实体类中声明的结构完全一致,否则Room就会抛
* 出异常。
*/
private val MIGRATION_1_2 = object : Migration(1,2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("create table Book (id integer primary" +
"key autoincrement not null, name text not null," +
"pages integer not null)")
}
}
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") // 数据库名称
.addMigrations(MIGRATION_1_2) // 传入升级方案
.build().apply { instance = this }
}
}
}
修改表结构
添加author字段
package com.jpc.jetpackapp
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Book(var name: String, var pages: Int, var author: String) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
package com.jpc.jetpackapp
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
// 声明数据库版本号及包括哪些实体,多个实体使用逗号隔开
@Database(version = 3, entities = [User::class, Book::class])
abstract class AppDatabase: RoomDatabase() {
// 只需要声明一个抽象方法,用于获取Dao的实例,不需要实现具体逻辑,由Room底层实现了
abstract fun userDao(): UserDao
abstract fun bookDao(): BookDao
companion object{
/**
* 在companion object结构体中,我们实现了一个Migration的
* 匿名类,并传入了1和 2这两个参数,表示当数据库版本从1升级到2的时候就执行这个匿名类中
* 的升级逻辑。匿名类实例的变量命名也比较有讲究,这里命名成MIGRATION_1_2,可读性更
* 高。由于我们要新增一张Book表,所以需要在migrate()方法中编写相应的建表语句。另外必
* 须注意的是,Book表的建表语句必须和Book实体类中声明的结构完全一致,否则Room就会抛
* 出异常。
*/
// 添加表
private val MIGRATION_1_2 = object : Migration(1,2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("create table Book (id integer primary" +
"key autoincrement not null, name text not null," +
"pages integer not null)")
}
}
// 修改表
private val MIGRATION_2_3 = object : Migration(2,3) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("alter table Book add column author text not null default 'unknown'")
}
}
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") // 数据库名称
.addMigrations(MIGRATION_1_2, MIGRATION_2_3) // 传入升级方案
.build().apply { instance = this }
}
}
}
5、WorkManager
(1)简单使用
添加依赖
implementation( "androidx.work:work-runtime:2.9.0") // WorkManager
WorkManager的基本用法其实非常简单,主要分为以下3步:
(1) 定义一个后台任务,并实现具体的任务逻辑;
(2) 配置该后台任务的运行条件和约束信息,并构建后台任务请求;
(3) 将该后台任务请求传入WorkManager的enqueue()方法中,系统会在合适的时间运行。
package com.jpc.jetpackapp
import android.content.Context
import android.util.Log
import androidx.work.Worker
import androidx.work.WorkerParameters
/**
* 后台任务的写法非常固定,也很好理解。首先每一个后台任务都必须继承自Worker类,并调用
* 它唯一的构造函数。然后重写父类中的doWork()方法,在这个方法中编写具体的后台任务逻辑即可。
* doWork()方法不会运行在主线程当中,因此你可以放心地在这里执行耗时逻辑,不过这里简单
* 起见只是打印了一行日志。另外,doWork()方法要求返回一个Result对象,用于表示任务的
* 运行结果,成功就返回Result.success(),失败就返回Result.failure()。除此之外,
* 还有一个Result.retry()方法,它其实也代表着失败,只是可以结合
* WorkRequest.Builder的setBackoffCriteria()方法来重新执行任务,我们稍后会进行学习。
*/
class SimpleWorker (context: Context, params: WorkerParameters): Worker(context, params){
override fun doWork(): Result {
Log.d("SimpleWorker", "do something in SimpleWorker")
return Result.success()
}
}
val doWorkBtn = findViewById<Button>(R.id.doWorkBtn)
/**
* OneTimeWorkRequest.Builder是WorkRequest.Builder的子类,用于构建单次运行的
* 后台任务请求。WorkRequest.Builder还有另外一个子类
* PeriodicWorkRequest.Builder,可用于构建周期性运行的后台任务请求,但是为了降低
* 设备性能消耗,PeriodicWorkRequest.Builder构造函数中传入的运行周期间隔不能短于15分钟
*/
doWorkBtn.setOnClickListener {
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()
val periodicWorkRequest =
PeriodicWorkRequest.Builder(SimpleWorker::class.java, 15, TimeUnit.MINUTES).build()
WorkManager.getInstance(this).enqueue(request)
}
(2)处理复杂任务
让后台任务在指定的延迟时间后运行,只需要借助
setInitialDelay()方法就可以了
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(5, TimeUnit.MINUTES) // 延迟5min执行
.build()
给后台任务请求添加标签
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setInitialDelay(5, TimeUnit.MINUTES) // 延迟5min执行
.addTag("Simple") // 添加标签
.build()
最主要的一个功能就是我们可以通过标签来取消后台任务请
求
WorkManager.getInstance(this).cancelAllWorkByTag("Simple")
// 一次性取消所有的后台任务
WorkManager.getInstance(this).cancelAllWork()
如果后台任务的doWork()方法中返回了Result.retry(),
那么是可以结合setBackoffCriteria()方法来重新执行任务的
/**
setBackoffCriteria()方法接收3个参数:第二个和第三个参数用于指定在多久之后重新执
行任务,时间最短不能少于10秒钟;第一个参数则用于指定如果任务再次执行失败,下次重试
的时间应该以什么样的形式延迟。这其实很好理解,假如任务一直执行失败,不断地重新执行
似乎并没有什么意义,只会徒增设备的性能消耗。而随着失败次数的增多,下次重试的时间也
应该进行适当的延迟,这才是更加合理的机制。第一个参数的可选值有两种,分别是LINEAR和
EXPONENTIAL,前者代表下次重试时间以线性的方式延迟,后者代表下次重试时间以指数的方
式延迟
*/
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
.setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS) // 重试
.build()
doWork()方法中返回Result.success()和Result.failure()又有什么作用?这两个返回值其实就是用于通知任务运行结果的,我们可以使用如下代码对后台任务的运行结果进行监听。
也可以调用getWorkInfosByTagLiveData()方法,监听同一标签名下所有后台任务请求的运行结果。
/**
调用了getWorkInfoByIdLiveData()方法,并传入后台任务请求的id,会返回一个LiveData对象。然后我们就可以调用LiveData对象的observe()方法来观察数据变化了,以此监听后台任务的运行结果。
*/
WorkManager.getInstance(this)
.getWorkInfoByIdLiveData(request.id)
.observe(this){workInfo ->
when(workInfo.state){
WorkInfo.State.SUCCEEDED -> Log.d("MainActivity", "success")
WorkInfo.State.FAILED -> Log.d("MainActivity", "failed")
else -> {
Log.d("MainActivity", "bug")
}
}
}
WorkManager中比较有特色的一个功能——链式任务。
假设这里定义了3个独立的后台任务:同步数据、压缩数据和上传数据。现在我们想要实现先同步、再压缩、最后上传的功能,就可以借助链式任务来实现,代码示例如下。
val sync = ...
val compress = ...
val upload = ...
WorkManager.getInstance(this)
.beginWith(sync)
.then(compress)
.then(upload)
.enqueue()
beginWith()方法用于开启一个链式任务,至于后面要接上什么样的后台任务,只需要使用then()方法来连接即可。另外WorkManager还要求,必须在前一个后台任务运行成功之后,下一个后台任务才会运行。也就是说,如果某个后台任务运行失败,或者被取消了,那么接下来的后台任务就都得不到运行了。