Jetpack简介
Jetpack是一个开发组件工具集,它的主要目的是帮助开发者编写更加简洁的代码,并简化开发过程。另外,它不依赖于任何Android系统版本,拥有很好的向下兼容性。
ViewModel
ViewModel的作用就是可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据的。也就是说,只要是界面上能看得到的数据,它的相关变量都应该存放在ViewModel中,而不是Activity中,这样可以在一定程度上减少Activity中的逻辑。 另外,ViewModel的生命周期和Activity不同,比如,当手机发生横竖屏旋转的时候,Activity会被重新创建,同时存放在Activity中的数据也 会丢失。而ViewMode可以保证在手机屏幕发生旋转的时候不会被重新创建,只有当Activity退出的时候才会跟着Activity一起销毁。因此,将与界面相关的变量存放在ViewModel当中,这样即使旋转手机屏幕,界面上显示的数据也不会丢失。
基本用法
在使用前要先添加依赖包
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
在activity_main.xml 中添加一个id 为infoText 的TextView 用于记数,再添加一个id 为plusOneBtn 的Button 用于让数字增加。 在新建一个MainViewModel 类
class MainViewModel :ViewModel() {
var counter = 0
}
在MainActivity 中添加具体逻辑
class MainActivity : AppCompatActivity() {
private lateinit var mainBinding: ActivityMainBinding
lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
mainBinding.plusOneBtn.setOnClickListener {
viewModel.counter ++
refreshCounter()
}
refreshCounter()
}
private fun refreshCounter() {
mainBinding.infoText.text = viewModel.counter.toString()
}
}
然后运行时,点击按钮后及其旋转屏幕也不会使记数消失。
注意
在使用时一定不能直接创建ViewModel 的实例,而是通过ViewModelProvider 来获取它的实例
ViewModelProvider(<Activity或Fragment的实例>).get(<ViewModel>::class.java)
因为ViewModel 有其独立的生命周期,并且其生命周期要长于Activity 。如果在onCreate() 方法中创建ViewModel 的实例,那么每次onCreate() 方法执行的时候,ViewModel 都会创建一个新的实例,这样当手机屏幕发生旋转的时候,就无法保留其中的数据了。
向ViewModel传递参数
借助ViewModelProvider.Factory 就可以实现了。 比如,实现退出程序后又重新打开的情况下,数据仍然不会丢失。实现这个功能需要在退出程序的时候对当前的计数进行保存,然后在重新打开程序的时候读取之前保存的计数,并传递给MainViewModel 。 首先需要修改MainViewModel 中的代码
class MainViewModel(countReserved: Int) : ViewModel() {
var counter = countReserved
}
再新建一个MainViewModelFactory 类
class MainViewModelFactory(private val countReserved: Int) : ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MainViewModel(countReserved) as T
}
}
然后在activity_main.xml 中添加一个id 为clearBtn 的按钮用于清零数据 最后修改MainActivity 中的代码
class MainActivity : AppCompatActivity() {
private lateinit var mainBinding: ActivityMainBinding
lateinit var viewModel: MainViewModel
lateinit var sp : SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
sp = getPreferences(Context.MODE_PRIVATE)
val countReserved = sp.getInt("count_reserved",0)
viewModel = ViewModelProvider(this,MainViewModelFactory(countReserved))
.get(MainViewModel::class.java)
mainBinding.plusOneBtn.setOnClickListener {
viewModel.counter ++
refreshCounter()
}
mainBinding.clearBtn.setOnClickListener {
viewModel.counter = 0
refreshCounter()
}
refreshCounter()
}
override fun onPause() {
super.onPause()
sp.edit{
putInt("count_reserved",viewModel.counter)
}
}
private fun refreshCounter() {
mainBinding.infoText.text = viewModel.counter.toString()
}
}
这样当运行时点击按钮退出后在进入记数依然保存。
Lifecycles
Lifecycles 可以让任何一个类都能轻松感知到Activity的生命周期,同时也不需要在Activity中编写大量的逻辑处理。 在使用时,要新创建一个名字为MyObserver 的类
class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun activityStart(){
Log.d("MyObserver","activity start")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun activityStop(){
Log.d("MyObserver","activity stop")
}
}
代码中的activityStart() 和activityStop() 法就应该分别在Activity 的onStart() 和onStop() 触发的时候执行,这个时候就得借助LifecycleOwner 了
lifecycleOwner.lifecycle.addObserver(MyObserver())
虽然可以自己去实现一个LifecycleOwner ,但通常情况下这是完全没有必要的。因为只要是继承自AppCompatActivity 的Activity ,或是继承自androidx.fragment.app.Fragment 的Fragment , 那么它们本身就是一个LifecycleOwner 的实例,这部分工作已经由AndroidX 库自动完成了。 所以只需要在想监听的activity 中加入一行代码就可以了 这样尝试运行和退出,就会有对应的日志打印。 此外,Lifecycle 还可以主动获知当前activity 的生命周期。 只需要在MyObserver 的构造函数中将Lifecycle 对象传进来即可。 有了Lifecycle 对象之后,就可以在任何地方调用lifecycle.currentState 来主动获知当前的生命周期状态。lifecycle.currentState 返回的生命周期状态是一个枚举类型,一共有INITIALIZED 、DESTROYED 、CREATED 、STARTED 、RESUMED ,5种状态。 比如,当返回值为CREATED 时,就说明onCreate() 方法已经执行了,但onStart() 方法还没执行。
LiveData
LiveData 是Jetpack 提供的一种响应式编程组件,它可以包含任何类型的数据,并在数据发生变化的时候通知给观察者。LiveData 特别适合与ViewModel 结合在一起使用。
基本用法
比如刚刚的计数,是在Activity 中手动获取ViewModel 中的数据这种交互方式,但是ViewModel 却无法将数据的变化主动通知给Activity 。 如果将计数器的计数使LiveData 来包装,然后在Activity 中去观察它,就可以主动将数据变化通知给Activity 了。
修改一下MainViewModel 中的代码
class MainViewModel(countReserved: Int) : ViewModel() {
var counter = MutableLiveData<Int>()
init {
counter.value = countReserved
}
fun plusOne(){
val count = counter.value ?:0
counter.value = count + 1
}
fun clear(){
counter.value = 0
}
}
注意:如果需要在子线程中给LiveData 设置数据,一定要调用postValue() 方法,而不能再使用setValue() 方法,否则会发生崩溃。
对应的再修改一下MainActivity 中的代码
class MainActivity : AppCompatActivity() {
private lateinit var mainBinding: ActivityMainBinding
lateinit var viewModel: MainViewModel
lateinit var sp : SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
sp = getPreferences(Context.MODE_PRIVATE)
val countReserved = sp.getInt("count_reserved",0)
viewModel = ViewModelProvider(this,MainViewModelFactory(countReserved))
.get(MainViewModel::class.java)
mainBinding.plusOneBtn.setOnClickListener {
viewModel.plusOne()
}
mainBinding.clearBtn.setOnClickListener {
viewModel.clear()
}
viewModel.counter.observe(this, Observer { count ->
mainBinding.infoText.text = count.toString()
})
}
override fun onPause() {
super.onPause()
sp.edit{
putInt("count_reserved",viewModel.counter.value ?: 0)
}
}
}
这样就可以正常工作了。
但它还不是最规范的做法。 主要的问题就在于将counter 这个可变的LiveData 暴露给了外部。这样即使是在ViewModel 的外面也是可以给counter 设置数据的,从而破坏了ViewModel 数据的封装性,同时也可能带来一定的风险。 比较推荐的做法是,永远只暴露不可变的LiveData 给外部。这样在非ViewModel 中就只能观察LiveData 的数据变化,而不能给LiveData 设置数据。
class MainViewModel(countReserved: Int) : ViewModel() {
val counter: LiveData<Int>
get() = _counter
private val _counter = MutableLiveData<Int>()
init {
_counter.value = countReserved
}
fun plusOne(){
val count = _counter.value ?:0
_counter.value = count + 1
}
fun clear(){
_counter.value = 0
}
}
map
map() 方法的作用是将实际包含数据的LiveData 和仅用于观察数据的LiveData 进行转换。
比如,有一个User 类
data class User(var firstName: String, var lastName: String, var age:Int)
可以在ViewModel 中创建一个相应的LiveData 来包含User 类型的数据。 但如果MainActivity 中明确只会显示用户的姓名,不关心用户的年龄,那么这个时候还将整个User 类型的LiveData 暴露给外部,就不那么合适了。 map() 方法就是专门用于解决这种问题的,它可以将User 类型的LiveData 自由地转型成任意其他类型的LiveData : 调用了Transformations 的map() 方法来对LiveData 的数据类型进行转换。map() 方法接收两个参数:第一个参数是原始的LiveData 对象;第二个参数是一个转换函数,在转换函数里编写具体的转换逻辑即可。这里的逻辑也很简单,就是将User 对象转换成一个只包含用户姓名的字符串。 另外,将userLiveData 声明成了private ,以保证数据的封装性。外部使用的时候只要观察userName 这个LiveData 就可以了。当userLiveData 的数据发生变化时,map() 方法会监听到变化并执行转换函数中的逻辑,然后再将转换之后的数据通知给userName 的观察者。
switchMap
如果ViewModel 中的某个LiveData 对象是调用另外的方法获取的,那么就可以借助switchMap() 方法,将这个LiveData 对象转换成另外一个可观察的LiveData 对象。 因为如果调用 getUser()方法返回的都是一个新的LiveData实例,而上述写法会一直观察老的LiveData实例,从而根本无法观察到数据的变化。这种情况下的LiveData是不可观察的。
新建一个单例类Repository
object Repository {
fun getUser(userId: String): LiveData<User>{
val liveData = MutableLiveData<User>()
liveData.value =User(userId, userId, 0)
return liveData
}
}
然后在MainViewModel 中借助switchMap() 方法,将这个LiveData 对象转换成另外一个可观察的LiveData 对象。 这里定义了一个新的userIdLiveData 对象,用来观察userId 的数据变化,然后调用了Transformations 的switchMap() 方法,用来对另一个可观察的LiveData 对象进行转换。 为了测试代码是否可用,在activity_main.xml 中添加一个id 为getUserBtn 的按钮,并在MainActivity 中添加它的点击事件以及相关逻辑。
mainBinding.getUserBtn.setOnClickListener {
val userId = (0..10000).random().toString()
viewModel.getUser(userId)
}
viewModel.user.observe(this, Observer { user ->
mainBinding.infoText.text = user.firstName
})
这样在运行时多次点击按钮时,会发现屏幕上的数字一直随机变换。 即在按钮的点击事件中使用随机函数生成了一个userId ,然后调用MainViewModel 的getUser() 方法来获取用户数据,但是这个方法现在不会有任何返回值了。等数据获取完成之后,可观察LiveData 对象的observe() 方法将会得到通知,在这里将获取的用户名显示到界面上。
流程梳理
首先,当外部调用MainViewModel 的getUser() 方法来获取用户数据时,并不会发起任何请求或者函数调用,只会将传入的userId 值设置到userIdLiveData 当中。 一旦userIdLiveData 的数据发生变化,那么观察userIdLiveData 的switchMap() 方法就会执行,并且调用我们编写的转换函数。 然后在转换函数中调用Repository.getUser() 方法获取真正的用户数据。 同时,switchMap() 方法会将Repository.getUser() 方法返回的LiveData 对象转换成一个可观察的LiveData 对象,对于Activity 而言,只要去观察这个LiveData 对象就可以了。
Room
ORM(Object Relational Mapping)也叫对象关系映射。简单来讲,一般使用的编程语言是面向对象语言,而使用的数据库则是关系型数据库, 将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是ORM了。 ORM框架有一个强大的功能,就是可以用面向对象的思维来和数据库进行交互,绝大多数情况下不用再和SQL语句打交道了,同时也不用担心操作数据库的逻辑会让项目的整体代码变得混乱。 Room就是Android官方推出的一个ORM框架,并加入了Jetpack当中。
整体结构
Room主要由Entity、Dao和Database这3部分组成,每个部分都有明确的职责。
- Entity:用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并且表中的列是根据实体类中的字段自动生成的。
- Dao:是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。
- Database:用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提供Dao层的访问实例。
添加依赖
这里新增了一个kotlin-kapt 插件,同时在dependencies 闭包中添加了两个Room 的依赖库。由于Room 会根据在项目中声明的注解来动态生成代码,因此这里一定要使用kapt 引入Room 的编译时注解库,而启用编译时注解功能则一定要先添加kotlin-kapt 插件。 注意,kapt 只能在Kotlin 项目中使用,如果是Java 项目的话,使用annotationProcessor 即可。
基础用法 - 增删改查
定义实体类 - Entity
一个良好的数据库编程建议是,给每个实体类都添加一个id 字段,并将这个字段设为主键
这里只需修改之前User 类中的代码
@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
访问数据库的操作无非就是增删改查这4种,但是业务需求却是千变万化的。而Dao 要做的事情就是覆盖所有的业务需求,使得业务方永远只需要与Dao 层进行交互,而不必和底层的数据库打交道
新建一个UserDao 接口,注意必须使用接口。
@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 loadUsersOlder(age: Int): List<User>
@Delete
fun deleteUser(user: User)
@Query("delete from User where lastName = :lastName")
fun deleteUserByLastName(lastName: String): Int
}
UserDao 接的上面使用了一个@Dao 注解,这样Room 才能将它识别成一个Dao 。 UserDao 的内部就是根据业务需求对各种数据库操作进行的封装。数据库操作通常有增删改查这4种,因此Room 也提供了@Insert 、@Delete 、@Update 和@Query 这4种相应的注解。
- insertUser()方法上面使用了@Insert注解,表示会将参数中传入的User对象插入数据库中,插入完成后还会将自动生成的主键id值返回。
- updateUser()方法上面使用了@Update注解,表示会将参数中传入的User对象更新到数据库当中。
- deleteUser()方法上面使用了@Delete注解,表示会将参数传入的User对象从数据库中删除。
以上几种数据库操作都是直接使用注解标识即可,不用编写SQL语句。
但是如果想要从数据库中查询数据,或者使用非实体类参数来增删改数据,那么就必须编写SQL语句了。比如UserDao 接口中定义的loadAllUsers() 方法,用于从数据库中查询所有的用户,如果只使用一个@Query 注解,Room 将无法知道想要查询哪些数据,因此必须在@Query 注解中编写具体的SQL语句才行。
还可以将方法中传入的参数指定到SQL语句当中,比如loadUsersOlderThan() 方法就可以查询所有年龄大于指定参数的用户。另外,如果是使用非实体类参数来增删改数据,那么也要编写SQL语句才行,而且这个时候不能使用@Insert 、@Delete 或@Update 注解,而是都要使用@Query 注解才行,参考 deleteUserByLastName() 方法的写法。
定义Database - Database
这部分内容的写法是固定的,只需要定义好3个部分的内容:数据库的版本号、包含哪些实体类,以及提供Dao 层的访问实例。
新建一个AppDatabase.kt 文件
@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
}
}
}
}
在AppDatabase 类的头部使用了@Database 注解, 并在注解中声明了数据库的版本号以及包含哪些实体类,多个实体类之间用逗号隔开即可。
另外,AppDatabase 类必须继承自RoomDatabase 类,并且一定要使用abstract 关键字将它声明成抽象类,然后提供相应的抽象方法,用于获取之前编写的Dao 的实例,比如这里提供的userDao() 方法。不过只需要进行方法声明就可以了,具体的方法实现是由Room 在底层自动完成的。
紧接着,在companion object 结构体中编写了一个单例模式,因为原则上全局应该只存在一份AppDatabase 的实例。这里使用了instance 变量来缓存AppDatabase 的实例,然后在getDatabase() 方法中判断: 如果instance 变量不为空就直接返回,否则就调用Room.databaseBuilder() 方法来构建一个AppDatabase 的实例。
databaseBuilder() 方法接收3个参数,注意第一个参数一定要使用 applicationContext ,而不能使用普通的context ,否则容易出现内存泄漏的情况,第二个参数是AppDatabase 的Class 类型,第三个参数是数据库名。最后调用build() 方法完成构建,并将创建出来的实例赋值给instance 变量,然后返回当前实例即可。
在Activity中实现逻辑
在activity_main.xml 中添加4个按钮,其id 分别与对应的文字相同。
val userDao = AppDatabase.getDatabase(this).userDao()
val user1 = User("Tom","Brady",40)
val user2 = User("Tom","Hanks",63)
mainBinding.addDataBtn.setOnClickListener {
thread {
user1.id = userDao.insertUser(user1)
user2.id = userDao.insertUser(user2)
}
}
mainBinding.updateDataBtn.setOnClickListener {
thread {
user1.age = 42
userDao.updateUser(user1)
}
}
mainBinding.deleteDataBtn.setOnClickListener {
thread {
userDao.deleteUserByLastName("Hanks")
}
}
mainBinding.queryDataBtn.setOnClickListener {
thread {
for (user in userDao.loadAllUsers()){
Log.d("MainActivity.out",user.toString())
}
}
}
在Add Data 按钮的点击事件中,调用了UserDao 的insertUser() 方法,将这两个User 对象插入数据库中,并将insertUser() 方法返回的主键id 值赋值给原来的User 对象。之所以要这么做,是因为使用@Update 和@Delete 注解去更新和删除数据时都是基于这个id 值来操作的。
结果展示
这样在测试时:
- 红框:先点击
Add Data 按钮,再点击Query Data 按钮,就展示最开始添加的user1 、user2 - 黄框:先点击
Update Data 按钮,再点击Query Data 按钮,发现user1 中的年龄变成了42 - 绿框:先点击
delete Data 按钮,再点击Query Data 按钮,lastName 为hanks 的user 被删掉了
注意
数据库操作属于耗时操作,Room 默认是不允许在主线程中进行数据库操作的,因此上述代码将增删改查的功能都放到了子线程中。 不过为了方便测试,Room 还提供了一个更加简单的方法,如下所示:
Room.databaseBuilder(context.applicationContext,
AppDatabase::class.java,"app_database")
.allowMainThreadQueries()
.build()
在构建AppDatabase 实例的时候,加入一个allowMainThreadQueries() 方法,这样Room 就允许在主线程中进行数据库操作了。 这个方法建议只在测试环境下使用。
数据库升级
场景一:将数据库升级为2号版本,并添加一张Book表
首先要新建一个Book 实体类
@Entity
data class Book(var name: String,var pager: 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 编写数据库升级的逻辑
@Database(version = 2, entities = [User::class, Book::class])
abstract class AppDatabase : RoomDatabase(){
abstract fun userDao(): UserDao
abstract fun BookDao(): BookDao
companion object{
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
@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
}
}
}
}
首先在@Database 注解中,将版本号升级成了2,并将Book 类添加到了实体类声明中,然后又提供了一个bookDao() 方法用于获取BookDao 的实例。 在companion object 结构体中,实现了一个Migration 的匿名类,并传入了1和2这两个参数,表时当数据库版本从1升级到2的时候就执行这个匿名类中的升级逻辑。匿名类实例的变量命名也比较有讲究,这里命名成MIGRATION_1_2 ,可读性更高。 由于要新增一张Book 表,所以需要在migrate() 方法中编写相应的建表语句。另外必须注意的是,Book 表的建表语句必须和Book 实体类中声明的 结构完全?致,否则Room 就会抛出异常。 最后在构建AppDatabase 实例的时候,加入一个addMigrations() 方法,并把MIGRATION_1_2 传入即可。 现在进行任何数据库操作时,Room 就会自动根据当前数据库的版本号执行这些升级逻辑,从而让数据库始终保证是最新的版本。
场景二:将数据库升级为3号版本,并在表中添加新的一列
在Book 实体类中添加了作者字段时
@Entity
data class Book(var name: String,var pager: Int, var author: String){
@PrimaryKey(autoGenerate = true)
var id: Long =0;
}
对应的也要在AppDatabase 中加入第2次升级的逻辑:
WorkManager
WorkManager 很适合用于处理一些要求定时执行的任务,它可以根据操作系统的版本自动选择底层是使用AlarmManager 实现还是JobScheduler 实现,从而降低了开发者的使用成本。另外,它还支持周期性任务、链式任务处理等功能,是一个非常强大的工具。
但是,要先明确:WorkManager 和Service 并不相同,也没有直接的联系。 Service 是Android 系统的四大组件之一,它在没有被销毁的情况下是一直保持在后台运行的。而WorkManager 只是一个处理定时任务的工具,它可以保证即使在应用退出甚至手机重启的情况下,之前注册的任务仍然将会得到执行,因此WorkManager 很适合用于执行一些定期和服务器进行交互的任务,比如周期性地同步数据,等等。
另外,使用WorkManager 注册的周期性任务不能保证一定会准时执行,这并不是bug,而是系统为了减少电量消耗,可能会将触发时间临近的几个任务放在一起执行,这样可以大幅度地减少CPU被唤醒的次数,从而有效延长电池的使用时间
基本用法
在使用前要先添加依赖
implementation 'androidx.work:work-runtime:2.2.0'
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()
}
}
然后在activity_main.xml 中添加一个id 为doWorkBtn 的按钮,方便测试 在MainActivity 中完成最后两步
mainBinding.doWorkBtn.setOnClickListener {
val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()
WorkManager.getInstance(this).enqueue(request)
}
在运行时点击按钮,就会弹出日志
处理复杂任务
延迟执行
添加标签
根据标签,就可以批量取消任务
WorkManager.getInstance(this).cancelAllWorkByTag("simple")
但如果没有设置标签,也可以通过id单个取消
WorkManager.getInstance(this).cancelWorkById(request.id)
也可以一次性取消所有后台任务请求
WorkManager.getInstance(this).cancelAllWork()
重新执行
如果任务返回的是Resut.retry() ,可以通过红框中的语句重新执行
监听任务运行结果
WorkManager.getInstance(this)
.getWorkInfoByIdLiveData(request.id)
.observe(this) { workInfo ->
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
Log.d("MainActivity", "do work succeeded")
} else if (workInfo.state == WorkInfo.State.FAILED) {
Log.d("MainActivity", "do work failed")
}
}
链式任务
比如三个独立的后台任务要依次执行时
val work1 = ...
val work2 = ...
val work3 = ...
WorkManager.getInstance(this)
.beginWith(work1)
.then(work2)
.then(work3)
.enqueue()
|