Android Jetpack 组件

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还要求,必须在前一个后台任务运行成功之后,下一个后台任务才会运行。也就是说,如果某个后台任务运行失败,或者被取消了,那么接下来的后台任务就都得不到运行了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Innodb之redo日志

Innodb引擎执行流程 redo log ​ MySQL中的redo log&#xff08;重做日志&#xff09;是实现WAL&#xff08;预写式日志&#xff09;技术的关键组件&#xff0c;用于确保事务的持久性和数据库的crash-safe能力。借用《孔乙己》中酒店掌柜使用粉板记录赊账的故事&#xff0c;…

Flask前端页面文本框展示后端变量,路由函数内外两类

一、外&#xff01;路由函数外的前后端数据传输 Flask后端 ↓ 首先导入包&#xff0c;需要使用 后端&#xff1a;flask_socketio来进行路由外的数据传输&#xff0c; from flask_socketio import SocketIO, emit 前端&#xff1a;还有HTML头文件的设置。 <!DOCTYPE …

【云原生数据库:原理与实践】1- 数据库发展历程

1-数据库发展历程 1.1 数据库发展概述 从1960年&#xff1a;Integrated Database System&#xff08;IDS&#xff09;&#xff0c;该系统是一个网状模型&#xff08;Network Model&#xff09;到 IMS&#xff08;Information Management System&#xff09;&#xff0c;使用了…

Rust腐蚀服务器清档多教程

Rust腐蚀服务器清档多教程 大家好我是艾西&#xff0c;一个做服务器租用的网络架构师。上期教了大家怎么搭建服务器以及安装插件等那么随着大家自己架设服或是玩耍的时间肯定会有小伙伴想要去新增开区数量或是把原本的服务器进行一些调整等&#xff0c;那么今天主要聊的就是怎…

【智能算法】鸭群算法(DSA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2021年&#xff0c;Zhang等人受到自然界鸭群觅食行为启发&#xff0c;提出了鸭群算法&#xff08;Duck Swarm Algorithm, DSA&#xff09;。 2.算法原理 2.1算法思想 DSA基于自然界鸭群觅食过程&…

[leetcode] max-area-of-island

. - 力扣&#xff08;LeetCode&#xff09; 给你一个大小为 m x n 的二进制矩阵 grid 。 岛屿 是由一些相邻的 1 (代表土地) 构成的组合&#xff0c;这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0&#xff08;代表水&…

基于SpringBoot+Vue的共享汽车管理系统(源码+文档+包运行)

一.系统概述 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了共享汽车管理系统的开发全过程。通过分析共享汽车管理系统管理的不足&#xff0c;创建了一个计算机管理共享汽车管理系统的方案。文章介绍了共享汽车管理…

从 MySQL 到 DynamoDB,Canva 如何应对每天新增的 5000 万素材

作为一款设计工具&#xff0c;Canva 吸引人的一个重要特色就是拥有数以亿计的照片和图形资源&#xff0c;支持用户上传个人素材。 Canva 于 2013 年推出&#xff0c;设立了一个包含大量照片和图形的资源库&#xff0c;并允许用户上传自己的素材以用于设计。从发布之日起&#…

Python数学建模学习-PageRank算法

1-基本概念 PageRank算法是由Google创始人Larry Page在斯坦福大学时提出&#xff0c;又称PR&#xff0c;佩奇排名。主要针对网页进行排名&#xff0c;计算网站的重要性&#xff0c;优化搜索引擎的搜索结果。PR值是表示其重要性的因子。 中心思想&#xff1a; 数量假设&#…

JavaScript基础:js介绍、变量、数据类型以及类型转换

目录 介绍 引入方式 内部方式 外部形式 注释和结束符 单行注释 多行注释 结束符 输入和输出 输出 输入 变量 声明 赋值 关键字 变量名命名规则 常量 数据类型 数值类型 字符串类型 布尔类型 undefined 类型转换 隐式转换 显式转换 Number ✨介绍 &a…

typescript中的type关键字和interface关键字区别

Type又叫类型别名&#xff08;type alias&#xff09;,作用是给一个类型起一个新名字&#xff0c;不仅支持interface定义的对象结构&#xff0c;还支持基本类型、联合类型、交叉类型、元组等任何你需要手写的类型。 type num number; // 基本类型 type stringOrNum string |…

47.HarmonyOS鸿蒙系统 App(ArkUI)创建轮播效果

创建轮播效果&#xff0c;共3页切换 Entry Component struct Index {State message: string Hello Worldprivate swiperController: SwiperController new SwiperController()build() {Swiper(this.swiperController) {Text("第一页").width(90%).height(100%).bac…

BLE架构图

PHY层(Physical layer 物理层) PHY层用来指定BLE所用的无线频段(2.4G)&#xff0c;调制解调方式和方法、跳频等。PHY层的性能直接决定整个BLE芯片的功耗、灵敏度以及selectivity等射频指标。 LL层(Link Layer 链路层) 链路层主要是对RF射频控制。链路层定义了协议栈中最为基础的…

C++解决大学课设所有管理系统(增删查改)

C一篇解决大学课设所有**管理系统(增删查改) 文章目录 C一篇解决大学课设所有**管理系统(增删查改)1.引言1.1 使用结果展示 2. 基本原理3. 文件层次结构4.具体实现(通讯录管理系统为例)4.1 通讯录实体类(addressbook.h)4.2 通讯录实现类(addressbook.cpp)4.3 通讯录管理类&…

蓝桥杯 — — 完全日期

完全日期 友情链接&#xff1a;完全日期 题目&#xff1a; 思路&#xff1a; 直接从20010101枚举到20211231&#xff0c;然后再判断每一个数是否是一个合法的日期&#xff0c;如果这个日期是合法的&#xff0c;接着判断这个日期的每一个位置上的数字之和是否是一个完全平方数…

ChatGPT 可以预测未来吗?

推荐 4月13日的一篇有趣的 paper&#xff0c;特来分享。 &#x1f449; 当前的大型语言模型&#xff08;LLMs&#xff09;具有强大的数据合成和推理能力&#xff0c;但它们在直接预测尚未发生事件的准确性上常常受到限制。传统的预测方法依赖于直接询问模型关于未来的问题。 …

linux下安装nacos2.2.0

1、获取下载地址并下载 1.1、打开nacos官网 1.2、找到对应版本&#xff0c;点进去 ## 1.3、复制地址 1.4下载 # 进入要安装的目录&#xff0c;cd /usr/local/src # 执行wget https://github.com/alibaba/nacos/releases/download/2.2.0/nacos-server-2.2.0.tar.gz2、 安装…

【产品经理修炼之道】- 平台型产品经理与业务型产品经理

从2015年开始&#xff0c;阿里的产品经理招聘分为平台型和业务型&#xff0c;后来很多互金公司逐步学习采纳了这种分工方式&#xff0c;那么什么是平台型产品经理&#xff0c;什么是业务型产品经理呢&#xff1f;先上结论&#xff1a; 平台型产品经理&#xff08;产品设计&…

loD:如何实现代码的“高内聚、低耦合“

设计模式专栏&#xff1a;http://t.csdnimg.cn/3a25S 目录 1.引用 2.何为"高内聚、低耦合" 3.LoD 的定义描述 4.定义解读与代码示例一 5.定义解读与代码示例二 1.引用 本节介绍最后一个设计原则:LoD(Law of Demeter&#xff0c;迪米特法则)。尽LoD不像SOLID、KI…
最新文章