Jetpack笔记

Jetpack

简介

JetPack是一个开发组件工具集,主要目的是帮助我们编写出更加简洁的代码,并简化开发过程。JetPack组件一个特点是大部分绝不依赖于任何Android系统版本,这意味着这些组件通常是定义在AndroidX库当中,并且拥有非常好的向下兼容性。

目前Android官方最为推荐的项目框架就是MVVM,因为JetPack中的很多框架组件就是专门为MVVM架构量身打造的。

ViewModel

ViewModel算是JetPack中最重要的组件之一了。其一个重要作用就是可以帮助Activity分担一部分工作,它是专门用于存放与界面相关的数据的。也就是说,只要是界面上能看得到的数据,他的相关变量都应该存放在ViewModel中,而不是Activity中,这样一定程度上减少Activity中的逻辑。

ViewModel还有另一个非常重要的特性是ViewModel的生命周期和Activity不同,它可以保证在手机屏幕发生旋转时的时候不会被重新创建,只有当Activity退出的时候才会跟着Activity一起销毁。因此,将与界面相关的变量存在ViewModel中,这样即使旋转手机屏幕,界面上显示的数据也不会丢失。

基本用法

添加依赖:

dependencies{
    ...
    implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
}
//新建MainViewModel.kt
class MainViewModel : ViewModel() {
    var counter = 0
}

//MainActivity
class MainActivity : AppCompatActivity() {
    lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel=ViewModelProvider(this).get(MainViewModel::class.java)
        plusOneBtn.setOnClickListener {
            viewModel.counter++
            refreshCounter()
        }
        refreshCounter()
    }

    private fun refreshCounter() {
        infoText.text=viewModel.counter.toString()
    }
}

注意:不能直接去创建ViewModel的实例,而是一定要通过ViewModelProvider来获取ViewModel的实例

ViewModelProvider(<你的Activity或者是Fragment实例>).get(<你的ViewModel>::class.java)

之所以这么写是因为ViewModel具有其独立的生命周期,其生命周期长于Activity。如果在onCreate()中创建了ViewModel实例,那么每次onCreate()方法执行时候都会创建一个新的实例,这样就无法在手机旋转时保留数据了。

向ViewModel传递参数

可以借助ViewModelProvider.Factory就可以实现。

class MainViewModel (countReserved:Int): ViewModel() {
    var counter = countReserved
}

//新建MainViewModelFactory.kt
class MainViewModelFactory(private val countReserved:Int):ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return MainViewModel(countReserved) as T
    }
}

//MainActivity
class MainActivity : AppCompatActivity() {
    lateinit var viewModel: MainViewModel
    lateinit var sp:SharedPreferences

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sp=getPreferences(Context.MODE_PRIVATE)
        val countReserved=sp.getInt("count_reserved",0)
        viewModel=ViewModelProvider(this,MainViewModelFactory(countReserved))
        .get(MainViewModel::class.java)
        plusOneBtn.setOnClickListener {
            viewModel.counter++
            refreshCounter()
        }
        clearBtn.setOnClickListener { 
            viewModel.counter=0
            refreshCounter()
        }
        refreshCounter()
    }

    private fun refreshCounter() {
        infoText.text=viewModel.counter.toString()
    }

    override fun onPause() {
        super.onPause()
        sp.edit { 
            putInt("count_reserved",viewModel.counter)
        }
    }
}

ViewModelProvider.Factory接口要求必须实现create()方法,因此这里再create()方法中我们创建了MainViewModel的实例,并将countReserved参数穿了进去。由于create()方法的执行时机与Activity的生命周期无关,所以不会产生上述问题。

再onCreate()方法中,我们首先获取了SharedPreferences的实例,然后读取之前保存的计数值,如果没有读取到,就是用0作为默认值。接下来再ViewModelProvider的构造方法中额外传入一个MainViewModelFactory参数,这里将读取到的参数值传给了MainViewModelFactory的构造方法,只有这种方法才能将计数值最终传递给MainViewModel的构造方法。

Lifecycles

Lifecycles组件可以让任何一个类可以轻松感知到Activity的生命周期,同时又不需要再Activity中编写大量的逻辑处理。

//新建MyObserver.kt
class MyObserver:LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun activityStart(){
        Log.d("MyObserver", "activityStart: start")
    }
    
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun activityStop(){
        Log.d("MyObserver", "activityStop: stop")
    }
}

//MainActivity
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        lifecycle.addObserver(MyObserver())
    }
    
}

LifecycleObserver是一个空方法接口。同时要想感知到Activity的生命周期,需要借助额外的注解功能才行。

@OnLifecycleEvent注解,需要传入一种生命周期事件。生命周期事件一共分为七种ON_CREATE、ON_START、ON_RESUME、ON_PAUSE、ON_STOP、ON_DESTROY分别对应每种生命周期。ON_ANY可以匹配任何生命周期回调。

只要你的Activity时继承自AppCompatActivity或者你的Fragment继承自androidx.fragment.app.Fragment,那么他们本身就是一个LifecycleOwener实例。可以直接lifecycle.addObserver(MyObserver())来实现。

要想主动获知当前的生命状态,需要在MyObserver的构造函数中主动将Lifecycle对象传进来。

class MyObserver(val lifecycle:Lifecycle):LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun activityStart(){
        //lifecycle.currentSate 获知生命状态
        Log.d("MyObserver", "activityStart: start")
    }
    
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun activityStop(){
        Log.d("MyObserver", "activityStop: stop")
    }
}

LiveData

LiveData是JetPack提供的一种响应式编程组件,它可以包含任何类型的数据,并在数据发生变化的时候通知给观察者。LiveData特别适合与ViewModel结合在一起使用,虽然它也可以单独用在别的地方,但绝大多数都是使用在ViewModel当中的。

基本用法

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

//MainActivity
class MainActivity : AppCompatActivity() {
    lateinit var viewModel: MainViewModel
    lateinit var sp:SharedPreferences

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        sp=getPreferences(Context.MODE_PRIVATE)
        lifecycle.addObserver(MyObserver())
        val countReserved=sp.getInt("count_reserved",0)
        viewModel=ViewModelProvider(this,MainViewModelFactory(countReserved))
        .get(MainViewModel::class.java)
        plusOneBtn.setOnClickListener {
            viewModel.plusOne()
        }
        clearBtn.setOnClickListener {
            viewModel.clear()
        }
        viewModel.counter.observe(this, Observer { count->
            infoText.text=count.toString()
        })
    }

    override fun onPause() {
        super.onPause()
        sp.edit {
            putInt("count_reserved", viewModel.counter.value?:0)
        }
    }
}

MutableLiveData是一种可变的LiveData,它的用法主要有getValue()、setValue()和postValue()三种。getValue()主要用于获取LiveData中的数据,setValue()用于给数据赋值,但是只能在主线程中调用。postValue()则是用于在非主线程中给数据赋值。

调用viewModel.counter的observe()方法来观察数据变化,任何LiveData对象都可以调用它的observe()方法来观察数据的变化。observe()方法接收两个参数:一个是LifecycleOwner对象,因此可以直接传入this;第二个蚕食是一个Observer接口,当counter中包含数据发生变化时,就会回调到这里。

通过添加依赖:

dependencies{
    ...
    implementation"androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
}

可以实现如下语法结构:

viewModel.counter.observe(this){count->
     infoText.text=count.toString()
}

若想不暴露counter这个可变的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
    }
}

我们将原counter变为了_counter变量,并加上了private变为私有的。重新定义了一个counter变量,并将它声明为不可变的LiveData,并在它的get()属性方法中返回_counter变量。

map、switchMap

LiveData为了应付各种不同的需求,提供了两种转换方法:map()和switchMap()。

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

data class User(var firstName:String,var lastName:String,var age:Int)

class MainViewModel (countReserved:Int): ViewModel() {
    val userLiveData=MutableLiveData<User>()
    ...
}

如上所示,会暴露MainViewModel中的userLiveData,而如果MainActivity中明确只会显示用户的名字,而不完全关心用户年龄,这个时候还将整个User类型的LiveData暴露给外部就不那么合适。

class MainViewModel (countReserved:Int): ViewModel() {
    private val userLiveData=MutableLiveData<User>()
    val userName:LiveData<String> =Transformations.map(userLiveData){ user->
        "${user.firstName} ${user.lastName}"
    }
}

可以通过调用Transformations的map()方法来对LiveData的数据类型进行转换。map()方法接收两个参数:第一个参数是原始的LiveData对象;第二个参数是一个转换函数,我们在转换函数里编写具体的转换逻辑即可。这里的逻辑就是将User对象转换成一个只包含用户民资的字符串。

同时将userLiveData声明为了private,以保证数据的封装性。外部使用只要观察userName这个LiveData就行。当userLiveData的数据发生变化,map()方法会见听到变化并执行转换函数中的逻辑,然后再将转换之后的数据通知给userName的观察者。

switchMap()方法的使用场景非常固定,但是可能比map()方法更加实用。

LiveData对象的实例可能在ViewModel中创建,也可能ViewModel中的LiveData对象是调用另外的方法获取的。例如:

object Repository {
    fun getUser(userId:String):LiveData<User>{
        //userId应该用来从服务器获取User数据
        val liveData=MutableLiveData<User>()
        liveData.value= User(userId,userId,0)
        return liveData
    }
}

class MainViewModel (countReserved:Int): ViewModel() {
    ...
    fun getUser(userId:String):LiveData<User>{
        return Repository.getUser(userId)
    }
}

注意的是:getUser()方法返回的是一个包含User数据的LiveData对象,而且每次调用getUser()都会返回一个新的LiveData()对象。

如果仍然用viewModel.counter.observe(this){count->infoText.text=count.toString()}来观察,是错误的,因为次调用getUser()都会返回一个新的LiveData()对象,而上述一直在观察老的LiveData实例,无法察觉到数据的变化。

此时switchMap()方法可以派上用场。如果ViewModel中的某个LiveData对象是调用另外的方法来获取的,那么我们就可以借助switchMap()方法,将这个LiveData对象转换成另外一个可观察的LiveData对象。

class MainViewModel (countReserved:Int): ViewModel() {
    private val userIdLiveData=MutableLiveData<String>()
    val user:LiveData<User> =Transformations.switchMap(userIdLiveData){ userId->
        Repository.getUser(userId)
    }

    fun getUser(userId:String){
        userIdLiveData.value=userId
    }
}

这里定义了一个新的userIdLiveData对象,用来观察userId数据的变化,然后调用Transformations的switchMap()方法,用来对另一个可观察的LiveData对象进行转换。

switchMap()方法接收两个参数:第一个参数传入新增的userIdLiveData,switchMap()方法会对它进行观察;第二个参数是一个转换函数,注意,我们必须在这个转换函数中返回一个LiveData对象,因为switchMap()方法的工作原理就是要将转换函数中返回的LiveData对象转换成另一个可观察的LiveData对象。那么显然,只需要在转换函数中调用Repository.getUser()方法来得到LiveData对象,并将它返回就可。

即:当外部调用MainViewModel的getUser()方法来获取用户数据时,并不会发起任何请求或者函数调用,只会将传入的userId值设置到userIdLiveData当中。一旦userIdLiveData的数据发生变化,那么观察userIdLiveData的switchMap()方法就会执行,并且调用我们编写的转换函数,然后在转换函数中调用Repository.getUser()方法来获取真正的用户数据。同时,switchMap()方法会将Repository.getUser()方法返回的LiveData对象转换成一个可观察的LiveData对象,对于Activity而言,只要Activity而言,只要去观察这个LiveData对象就可以了。

若ViewModel中某个获取数据的方法有可能没有参数,该如何?在没有可观察数据的这种情况下需要创建一个空的LiveData对象:

class MyViewModel :ViewModel() {
    private val refreshLiveData=MutableLiveData<Any?>()

    val refreshResult=Transformations.switchMap(refreshLiveData){
        Repository.refresh()
    }

    fun refresh(){
        refreshLiveData.value=refreshLiveData.value
    }
}

关键在于 refreshLiveData.value=refreshLiveData.value。只需要让refreshLiveData重新自己赋值一次引起数据变动就可。

为了减少性能消耗,当Activity处于不可见状态的时候(比如手机息屏,或者被其他的Activity遮挡),如果LiveData中的数据发生了变化,是不会通知给观察者的。只有当Activity重新恢复可见状态时,才会将数据通知给观察者。假若在Activity 处于不可见状态时,LiveData发生了多次数据变化,当Activity 恢复可见时,只有最新的那份数据才会通知给观察者,之前的都算是过期了,直接被丢弃。

Room

基本使用

ORM也叫对象关系映射。将面向对象的语言和面向关系的数据库之间建立一种映射关系,就是ORM。使用ORM框架的好处是能够用面向对象的思维来和数据库进行交互没绝大多数情况下不用再和SQL语句打交道了,同时也不用担心操作数据库的逻辑会让项目的整体代码变得混乱。

Room是Android官方推出的一个ORM框架,并加入到了JetPack中。

Room由Entity、Dao和Database组成。

Entity:用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并且表中的列是根据实体类中的字段自动生成的。
Dao:Dao是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编程时,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互。
Database:用于定义数据库中的关键信息,包括数据库的版本号、包含那些实体类已经提供Dao层的访问实例。

使用Room需要先添加依赖:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' //添加这行

dependencies{
    ...
    implementation "androidx.room:room-runtime:2.1.0"
    kapt "androidx.room:room-compiler:2.1.0"
}

注意:kapt只能在Kotlin项目中使用,Java项目的话使用annotationProcessor即可。

@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
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 deleteUserBtLastName(lastName:String):Int
    
}

UserDao接口上使用了一个@Dao注解。UserDao的内部就是根据业务需求对数据库操作进行的封装。数据库操作通常有:@Insert 、@Update 、@Query 和@Delete,分别对应插、改、差和删。

@Insert注解会将参数传入数据库中,插入完成后会将自动生成的主键id值返回。

但是如果想要从数据库中查询数据,或者使用非实体类参数来增改数据,那么就必须编写SQL语句了。

@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类的实例,只需声明即可,具体的实现在底层会自动完成。

然后再companion object结构体中编写了一个单例模式,因为原则上全局只存在一份AppDatabase实例。这里使用instance变量来缓存AppDatabase的实例,然后在getDatabase()方法中判断:如果instance变量不为空就直接返回,否则就调用Room.databaseBuilder()方法来构建一个AppDatabase的实例。databaseBuilder()方法接收三个参数:第一个参数一定要使用applicationContext,而不能使用普通的Context,否则容易出现内存泄漏的情况。第二个参数是AppDatabase的Class类型,第三个参数是数据库类名。最后调用build()方法完成构建,并将创建出的实例赋值给instance变量,然后返回当前实例即可。

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val userDao=AppDatabase.getDatabase(this).userDao()
        val user1=User("Tom ","Brady",40)
        val user2=User("Tom ","Hanks",63)
        addData.setOnClickListener { 
            thread { 
                user1.id=userDao.insertUser(user1) //之所以要将insertUser()放回的主键id值付给原来的User对象,是因为使用@Update和@Delete注解去更新和删除数据时都是基于这个id值来操作的。
                user2.id=userDao.insertUser(user2)
            }
        }
        updateData.setOnClickListener {
            thread {
                user1.age=42
                userDao.updateUser(user1)
            }
        }
        deleteData.setOnClickListener {
            thread {
                userDao.deleteUserBtLastName("Hanks")
            }
        }
        queryData.setOnClickListener {
            thread {
                for (user in userDao.loadAllUsers()) {
                    Log.d("MainActivity", user.toString())
                }
            }
        }
    }

}

另外,由于数据库操作属于耗时操作,Room默认不允许在主线程进行数据库操作的,因此将增删查改的功能都放到了子线程中。为了方便测试,Room还提供了一个更简单的方法:

Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
    .allowMainTHreadQueries()
    .build()

在构建AppDatabase实例的时候,加入一个.allowMainTHreadQueries()方法,这样Room就允许在主线程中进行数据库操作了。

Room的数据库升级

Room数据升级非常繁琐,不过有供测试阶段使用的方法:

Room.databaseBuilder(context.applicationContext,AppDatabase::class.java,"app_database")
    .fallbackToDestructiveMigration()
    .build()

在构建AppDatabase时,加入一个fallbackToDestructiveMigration()方法。这样只要数据库进行升级,Room就会将当前的数据库销毁,然后再重新创建,随之而来的副作用就是将之前数据库中的所有数据全都丢失。

//新建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>
}

@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实体类的声明结构完全一致。

最后在构建AppDatabase的实例时,加入一个addMigrations()方法,并把MIGRATION_1_2传入即可。

不够由于每次数据可升级并不一定要新增一张表,可能是添加列。这种情况需要alert语句修改表结构。

@Entity
data class Book(var name:String,var pages:Int,var author:String) {
    @PrimaryKey(autoGenerate = true)
    var id:Long=0
}

@Database(version = 3,entities = [User::class,Book::class])
abstract class AppDatabase :RoomDatabase(){
    abstract fun userDao():UserDao

    abstract fun bookDao():BookDao

    companion object{

        val MIGRATION_2_3=object :Migration(2,3){
            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)")
                database.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_2_3).build().apply {
                instance=this
            }
        }
    }
}

WorkManager

为了解决保证应用程序在不同系统版本上的兼容性,Google推出了WorkManager组件。WorkManager很适合处理一些要求定时执行的任务,它可以根据操作系统的版本自动选择底层是使用AlarmManager还是JobScheduler实现,从而降低了我们的使用成本。此外其还支持周期性任务、链式任务处理等功能。

需要注意的是:WorkManager和Service并不相同,也没有直接的联系。Service是Android系统的四大组件之一,他在没有被销毁的情况下是一直保持在后台运行的。而WorkManager只是一个处理定时任务的工具,它可以保证在即使在应用推出甚至手机重启的情况下,之前注册的任务仍能够得到执行,因此WorkManager很适合用于执行一些定期和服务器进行交互的任务,比如周期性地同步数据等等。

另外使用WorkManager注册的周期任务也不能保证一定会准时执行,而是因为系统为了减少电量消耗,可能会将触发时间临近的几个任务放在一起执行,这样可以大幅度地减少CPU被唤醒的次数,从而有效延长电池的使用时间。

基本用法

需要添加依赖:

dependencies{
    ...
    implementation "androidx.work:work-runtime:2.2.0"
}

WorkManager的基本用法基本分为三步:

(1)定义一个后台任务,并实现具体的任务逻辑

(2)配置该后台任务的运行条件和约束信息,并构建后台任务请求。

(3)将该后台任务请求传入WorkManager的enqueue()方法中,系统会在合适的时间运行。

//定义一个后台任务
class SimpleWorker(context: Context,params:WorkerParameters):Worker(context,params) {
    override fun doWork(): Result {
        Log.d("SimpleWorker", "doWork in SimpleWorker")
        return Result.success()
    }
}

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()

//val request = PeriodicWorkRequest.Builder(SimpleWorker::class.java,15,TimeUnit.MINUTES).build()

WorkManager.getInstance(context).enqueue(request)

//Test
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        doWork.setOnClickListener { 
            val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java).build()
            WorkManager.getInstance(this).enqueue(request)
        }
    }

每个后台任务都继承自Worker类,并调用它唯一的构造函数。需要重写父类中的doWork()方法。doWork()方法不会运行在主线程,因此可以在这里执行耗时逻辑。doWork()方法需要返回Result对象,表示任务的运行结果。Result.success()是成功,Result.failure()是失败。Result.retry()也是失败,只是可以结合WorkRequest.Builder的setBackoffCriteria()方法来重新执行任务。

将创建的后台任务所对应的Class对象传入OneTimeWorkRequest.Builder的构造函数中,然后调用build()创建即可。

OneTimeWorkRequest.Builder是 WorkRequest.Builder的子类,用于创建单次运行的后台任务请求。WorkRequest.Builder还有另一个子类PeriodicWorkRequest.Builder,可用于构建周期性运行的后台任务请求,但是为了降低设备性能消耗,PeriodicWorkRequest.Builder构造函数传入的运行周期间隔不能短于15min。

处理复杂任务

val request = OneTimeWorkRequest.Builder(SimpleWorker::class.java)
    .setInitialDelay(5,TimeUnit.MINUTES)
    .addTag("simple") //给后台任务请求添加标签,可通过标签来取消后台任务
    .setBackoffCriteria(BackoffPolicy.LINEAR,10,TimeUnit.SECONDS)
    .build()

WorkManager.getInstance(this).cancelAllWorkByTag("simple")//通过标签来取消后台任务,标签可批量取消
WorkManager.getInstance(this).cancelWorkById(request.id)//通过id来取消后台任务,id只能取消单个任务
WorkManager.getInstance(this).cancelAllWork()//取消全部后台任务

借助setInitialDelay()方法来让后台任务在指定的延迟时间后运行。这表示我们希望让SimpleWorker后台任务在五分钟后运行,可以自由选择时间的单位(毫秒、秒、分钟、小时、天)。

如果后台任务的doWork()方法中返回了Result.retry(),那么是可以结合setBackoffCriteria()方法来重新执行任务的。setBackoffCriteria()方法接收三个参数:第二个和第三个参数用于指定在多久之后重新执行任务,时间最短不能少于10秒钟;第一个参数则用于指定如果任务再次执行失败,下次重试的时间应该以什么样的形式延迟。有LINEAR和EXPONENTIAL两种取值。前者表示下次重试时间以线性的方式延迟,后者表示重试时间以指数型方式延迟。

    WorkManager.getInstance(this).getWorkInfoByIdLiveData(request.id).observe(this){ workInfo->
                if (workInfo.state==WorkInfo.State.SUCCEEDED)
                    Log.d("MainActivity", " SUCCEEDED")
                else if (workInfo.state==WorkInfo.State.FAILED)
                    Log.d("MainActivity", " FAILED")
            }
        }

通过doWork()方法返回的Result.success()和Result.failure()来进行逻辑处理。

链式任务:

val sync=...
val compress=...
val upload=...
WorkManager.getInstance(this)
    .beginWith(sync) //同步数据
    .then(compress) //压缩数据
    .then(upload)   //上传数据
    .enqueue()

beginWith()方法用于开启一个链式任务,后边接着跟上then()方法来连接即可。另外,必须是前一个后台任务运行成功之后,下一个后台任务才会运行。如果前任务失败或者取消,后任务就不会被执行。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342