Android Jetpack 组件

发布于:2024-04-18 ⋅ 阅读:(31) ⋅ 点赞:(0)
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还要求,必须在前一个后台任务运行成功之后,下一个后台任务才会运行。也就是说,如果某个后台任务运行失败,或者被取消了,那么接下来的后台任务就都得不到运行了。


网站公告

今日签到

点亮在社区的每一天
去签到