Android Jetpack 组件

1、ViewModel

用于将数据与Activity分离,这样在Activity声明周期中,数据不会丢失。

(1)简单使用
    implementation ("androidx.lifecycle:lifecycle-extensions:2.2.0") // 使用ViewModel组件需要额外添加
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/infoText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:textSize="32sp"/><Buttonandroid: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.jetpackappimport androidx.lifecycle.ViewModel// 与MainActivity有关的数据
class MainViewModel: ViewModel() {var counter = 0;
}
package com.jpc.jetpackappimport androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.lifecycle.ViewModelProviderclass MainActivity : AppCompatActivity() {private lateinit var viewModel: MainViewModelprivate lateinit var infoText: TextViewoverride 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.jetpackappimport androidx.lifecycle.ViewModelclass MainViewModel(private val counterReserved: Int): ViewModel() {var counter = counterReserved; // counterReserved用于记录之前保存的数据
}
package com.jpc.jetpackappimport 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.jetpackappimport 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.ViewModelProviderclass MainActivity : AppCompatActivity() {private lateinit var viewModel: MainViewModelprivate lateinit var infoText: TextViewprivate lateinit var sp: SharedPreferencesoverride 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 = 0refreshCounter()}refreshCounter()}private fun refreshCounter(){infoText.text = viewModel.counter.toString()}override fun onPause() {super.onPause()// 保存数据到SharedPreferencesp.edit{putInt("count_reserved", viewModel.counter)}}
}
2、Lifecycles

用于感知Activity的生命周期

package com.jpc.jetpackappimport 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.jetpackappimport 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() {// 暴露给外部不可变的LiveDataval 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.jetpackappimport 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.ViewModelProviderclass MainActivity : AppCompatActivity() {private lateinit var viewModel: MainViewModelprivate lateinit var infoText: TextViewprivate lateinit var sp: SharedPreferencesoverride 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()}// 传入lifecyclelifecycle.addObserver(MyObserver(lifecycle))}override fun onPause() {super.onPause()sp.edit{putInt("count_reserved", viewModel.counter.value ?: 0)}}
}
(2)转换数据

map()方法,这个方法的作用是将实际包含数据的LiveData和仅用于观察数据的LiveData进行转换。

package com.jpc.jetpackappdata class User(var firstName: String, var lastName: String, var age: Int)
package com.jpc.jetpackappimport 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.jetpackappimport androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveDataobject 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.jetpackappimport androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.map
import androidx.lifecycle.switchMapclass 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 falseid("org.jetbrains.kotlin.android") version "1.9.22" apply falseid("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 可以显著提高性能
}
    // 使用Roomval 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.jetpackappimport 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.jetpackappimport androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update@Dao // 表示为Dao层
interface UserDao {@Insertfun insertUser(user: User): Long@Updatefun 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>@Deletefun 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 = 40userDao.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 {@Insertfun insertBook(book: Book): Long@Query("select * from Book")fun loadAllBooks(): List<Book>
}
package com.jpc.jetpackappimport 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(): UserDaoabstract fun bookDao(): BookDaocompanion 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@Synchronizedfun 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.jetpackappimport 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.jetpackappimport 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(): UserDaoabstract fun bookDao(): BookDaocompanion 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@Synchronizedfun 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.jetpackappimport 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还要求,必须在前一个后台任务运行成功之后,下一个后台任务才会运行。也就是说,如果某个后台任务运行失败,或者被取消了,那么接下来的后台任务就都得不到运行了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/820479.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

必应bing竞价广告推广开户联系方式?

随着互联网广告市场的日益繁荣与细分&#xff0c;必应Bing作为全球重要的搜索引擎之一&#xff0c;在国内市场也逐渐展现出强大的潜力与吸引力。越来越多的企业开始关注并探索必应Bing搜索广告所带来的巨大商机。其中&#xff0c;云衔科技以其卓越的专业素养和全面的服务体系&a…

stable diffusion--小白学习步骤

1.看一下Unet网络的讲解_哔哩哔哩_bilibili&#xff0c;了解Unet网络 2.看一下【生成式AI】Diffusion Model 原理剖析 (1/4)_哔哩哔哩_bilibili&#xff0c;起码要看前3/6个视频 3.看一下超详细的扩散模型&#xff08;Diffusion Models&#xff09;原理代码 - 知乎 (zhihu.co…

鑫鹿助贷CRM系统:助力助贷行业实现智能商业转型

数字化时代&#xff0c;商业竞争愈发激烈&#xff0c;助贷行业如何把握商机、实现高效管理、打造高回报率的商业模式&#xff0c;成为了助贷行业老板们比较关注的问题&#xff0c;而鑫鹿助贷CRM管理系统&#xff0c;正是这场商业变革中的得力助手&#xff0c;系统功能完善&…

途游游戏,科锐国际(计算机类),快手,得物,蓝禾,奇安信,顺丰,康冠科技,金证科技24春招内推

途游游戏&#xff0c;科锐国际&#xff08;计算机类&#xff09;&#xff0c;快手&#xff0c;得物&#xff0c;蓝禾&#xff0c;奇安信&#xff0c;顺丰&#xff0c;康冠科技&#xff0c;金证科技24春招内推 ①得物 【岗位】技术&#xff0c;设计&#xff0c;供应链&#xff0…

每帧纵享丝滑——ToDesk云电脑、网易云游戏、无影云评测分析及ComfyUI部署

目录 一、前言二、云电脑性能测评分析2.1、基本配置分析2.1.1、处理器方面2.1.2、显卡方面2.1.3、内存与存储方面2.1.4、软件功能方面 2.2、综合跑分评测 三、软件应用实测分析3.1、云电竞测评3.2、AIGC科研测评——ComfyUI部署3.2.1、下载与激活工作台3.2.2、加载模型与体验3.…

vba学习系列(4)-- index()提取指定单元格并保留字体格式

系列文章目录 文章目录 系列文章目录一、目标需求二、使用步骤1.VBA程序2.VBA简要程序 总结 一、目标需求 工作表2 B列中姓名&#xff0c;在工作表1 C列中存在相同姓名时&#xff0c;提取工作表2 AK列的对应单元格内容&#xff1b; 工作表2名称&#xff1a;OQC 工作表1名称&…

AGI的智力有可能在两年内超过人类水平

特斯拉CEO埃隆马斯克近日与挪威银行投资管理基金CEO坦根的访谈中表示&#xff0c;AGI的智力将在两年内可能超过人类智力&#xff0c;在未来五年内&#xff0c;AI的能力很可能超过所有人类。 马斯克透漏&#xff0c;去年人工智能发展过程中的主要制约因素是缺少高性能芯片&#…

基于springboot实现人事管理系统项目【项目源码+论文说明】

基于springboot实现人事管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;作为学校以及一些培训机构&#xff0c;都在用信息化战术来部署线上学习以及线上考试&#xff0c;可以与线下的考试有机的结合在一起&#xff0c;实现基于vue的人事系统在技术…

c++ 栈溢出问题

示例代码: #include <iostream> #include <chrono> #include <fstream>int main() {// 测量内存操作的执行时间int num = 1024 * 1024;int arry[num] = {2};int arry_tmp[num] = {0};std::ofstream outfile("data.bin", std::ios::binary | std:…

【LeetCode热题100】【二分查找】搜索二维矩阵

题目链接&#xff1a;74. 搜索二维矩阵 - 力扣&#xff08;LeetCode&#xff09; 在一个有序二维数组里面查找元素&#xff0c;同【LeetCode热题100】【矩阵】搜索二维矩阵 II-CSDN博客 如果用二分查找&#xff0c;时间复杂度是log(mn)&#xff0c;但是可以实现时间复杂度为O…

交通大模型与时序大模型整理【共15篇工作】【附开源代码】

随着城市化进程的加速和交通系统的不断发展&#xff0c;对交通数据和时序数据的整理与分析变得尤为重要。本文旨在探讨交通大模型与时序大模型的整理及其在城市规划、交通管理等领域的应用。交通大模型涉及交通流量、道路网络、交通规划等方面的数据&#xff0c;而时序大模型则…

代码随想录-算法训练营day10【栈与队列01:理论基础、用栈实现队列、用队列实现栈】

代码随想录-035期-算法训练营【博客笔记汇总表】-CSDN博客 第五章 栈与队列part01 ● day 1 任务以及具体安排&#xff1a;https://docs.qq.com/doc/DUG9UR2ZUc3BjRUdY ● day 2 任务以及具体安排&#xff1a;https://docs.qq.com/doc/DUGRwWXNOVEpyaVpG ● day 3 任务以及…

ZooKeeper临时有序节点生成过程以及序号超过最大值的处理思路

目录 ZooKeeper临时有序节点生成过程 ZooKeeper序号超过最大值的处理 ZooKeeper临时有序节点生成过程 创建节点时指定类型 当客户端向ZooKeeper请求创建节点时&#xff0c;需要指定节点类型。对于临时有序节点&#xff0c;应使用CreateMode.EPHEMERAL_SEQUENTIAL标志。这告诉…

IP定位技术原理详细阐述

IP定位技术原理主要基于IP地址与地理位置之间的关联&#xff0c;通过一系列的技术手段&#xff0c;实现对网络设备的物理位置进行精确或大致的定位。以下是对IP定位技术原理的详细阐述。 首先&#xff0c;我们需要了解IP地址的基本概念。IP地址是互联网协议地址的简称&#xff…

大模型日报|今日必读的10篇大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.谷歌推出新型 Transformer 架构&#xff1a;反馈注意力就是工作记忆 虽然 Transformer 给深度学习带来了革命性的变化&#xff0c;但二次注意复杂性阻碍了其处理无限长输入的能力。 谷歌研究团队提出了一种新型 T…

前端开发攻略---从源码角度分析Vue3的Propy比Vue2的defineproperty到底好在哪里。一篇文章让你彻底弄懂响应式原理。

1、思考 Vue的响应式到底要干什么&#xff1f; 无非就是要知道当你读取对象的时候&#xff0c;要知道它读了。要做一些别的事情无非就是要知道当你修改对象的时候&#xff0c;要知道它改了。要做一些别的事情所以要想一个办法&#xff0c;把读取和修改的动作变成一个函数&#…

xcode c++项目设置运行时参数

在 Xcode 项目中&#xff0c;你可以通过配置 scheme 来指定在运行时传递的参数。以下是在 Xcode 中设置运行时参数的步骤&#xff1a; 打开 Xcode&#xff0c;并打开你的项目。在 Xcode 菜单栏中&#xff0c;选择 "Product" -> "Scheme" -> "E…

前端实现下载的2种方法(个人总结)

一.后端在接口指明了下载的类型是blob类型 要实现下载项目数据并成为excel格式的 以这个接口为例: export const conversationDown () > {return http({url: /conversation/down,method: GET,responseType: blob}) } const handleDownload async () > {const res …

每日一练

这题我主要用的思想是:动态规划 1.状态表示&#xff1a;以i位置为结尾的字符串是否可以用字典表示&#xff0c;然后就可以拆分成 j ~ i 为字典中的最后一个单词&#xff0c;此时 0 < j < i (1.有可能全部为字典的一个单词&#xff0c;2.有可能只有一个字母的单词)&#x…

光纤网络的星际旅行:SFP与QSFP光模块的技术演进

&#x1f389;光模块作为完成光电转换的光器件&#xff0c;在光通信网络中必不可少&#xff0c;常见的有千兆/万兆光模块、SFP/SFP/QSFP28光模块等&#xff0c;那你知道这些光模块都是如何分类的吗&#xff1f;另外还有哪些类型&#xff1f;接下来我会在本文详细介绍光模块是如…