Android使用 WorkManager 调度任务

文章目录

  • 一、前言
  • 二、WorkManager入门指南
    • 2.1 添加依赖
    • 2.2 创建后台任务
    • 2.3 配置运行任务的方式和时间
    • 2.4 将任务提交给系统
  • 三、WorkManager 进阶
    • 3.1 设定任务请求
      • 3.1.1 任务约束
      • 3.1.2 初始延迟
      • 3.1.3 重试和退避政策
      • 3.1.4 定义任务的输入/输出
      • 3.1.5 标记任务
    • 3.2 任务的状态及任务状态跟踪
      • 3.2.1 任务的状态
      • 3.2.2 任务状态跟踪
    • 3.3 跟踪工作任务的中间状态
      • 3.3.1 更新进度
      • 3.3.2 跟踪任务进度
    • 3.4 将多个工作任务链接在一起
      • 3.4.1 创建工作任务链
      • 3.4.2 输入数据合并
      • 3.4.3 工作任务链的状态
    • 3.5 取消和停止工作任务
      • 3.5.1 取消工作任务
      • 3.5.2 停止正在运行的工作器
    • 3.6 重复性工作
    • 3.7 唯一工作任务
      • 3.7.1 创建唯一工作任务
      • 3.7.2 唯一工作链
    • 3.8 异步工作任务
  • 四、WorkManager 高级概念
    • 4.1 WorkManager配置和初始化
      • 4.1.1 WorkManager 2.1.0 及更高版本
        • 移除默认初始化程序
        • 实现 Configuration.Provider
      • 4.1.2 WorkManager 2.0.1 及更早版本
        • 默认初始化
        • 自定义初始化
    • 4.2 在 WorkManager 中进行线程处理
      • 4.2.1 在工作器中进行线程处理
      • 4.2.2 在 CoroutineWorker 中进行线程处理
      • 4.2.3 在 RxWorker 中进行线程处理
      • 4.2.4 在 ListenableWorker 中进行线程处理
  • 编后语

一、前言

    WorkManager属于Android Jetpack的一部分,通过WorkManager API可以轻松第调度可延迟的任务,即使是那些在应用退出或者设备重启时仍需要运行的任务。

WorkManager的主要功能

  • 最高可向后兼容到 API 14
    • 在 API 23 及以上级别的设备上运行使用 JobScheduler实现
    • 在 API 14-22 的设备上运行结合使用 BroadcastReceiver 和 AlarmManager实现
  • 支持网络可用性或充电状态等工作约束
  • 支持调度一次性任务和周期行的任务
  • 支持监控和管理计划任务
  • 将任务链接起来
  • 确保任务执行,即使应用退出或者设备重启也会执行任务
  • 遵循低电耗模式等省电功能

WorkManager的主要用途
    WorkManager主要用于可延迟执行的任务,并且在应用退出或者设备重启时必须能可靠运行的任务。例如:

  • 想后端发送日志或者分析数据
  • 定期同步应用数据到服务器

WorkManager的局限性
    WorkManager 不适用于应用进程结束时能够安全终止的运行中后台任务,也不适用于需要立即执行的任务。因此,也不要滥用WorkManager。

本文测试Demo源码下载

二、WorkManager入门指南

    WorkManager 的使用主要有以下几点操作:

  1. 将 WorkManager 添加到您的 Android 项目中(添加依赖)
  2. 创建后台任务
  3. 配置运行任务的方式和时间
  4. 将任务提交给系统

2.1 添加依赖

    WorkManager 是属于 Android Jetpack 的一部分,使用需要在应用模块下的build.gradle引入相关的依赖。

dependencies {
      def work_version = "2.3.4"

        // (仅Java使用,如果你的项目是基于Java开发的,引入这个)
        implementation "androidx.work:work-runtime:$work_version"

        // (Kotlin + coroutines,如果你的项目基于Kotlin开发,引入这个)
        implementation "androidx.work:work-runtime-ktx:$work_version"

        // 可选 - RxJava2 support
        implementation "androidx.work:work-rxjava2:$work_version"

        // 可选 - GCMNetworkManager support
        implementation "androidx.work:work-gcm:$work_version"

        // 可选 - 测试支持
        androidTestImplementation "androidx.work:work-testing:$work_version"
      }

说明:关于最新的WorkManager依赖,请参考: WorkManager 库版本历史

2.2 创建后台任务

    任务使用 Worker 类定义,类内部的doWork() 方法在 WorkManager 提供的后台线程上同步运行。要创建后台任务,扩展 Worker 类并重写 doWork() 方法即可。

示例:

class CheckSystemWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {

    override fun doWork(): Result {

        Log.e("TEST", "Checking system。。。。。。。。")
        Thread.sleep(3000)
        Log.e("TEST", "Checking system done.")
        return Result.success()
    }

}

2.3 配置运行任务的方式和时间

使用 Worker 定义任务之后,使用 WorkRequest 定义任务的运行方式和时间。任务可以是一次性的,也可以是周期性的。对于一次性 WorkRequest,请使用 OneTimeWorkRequest,对于周期性工作,请使用 PeriodicWorkRequest。

示例:

val checkSystem = OneTimeWorkRequestBuilder<CheckSystemWorker>().build()

2.4 将任务提交给系统

定义 WorkRequest 之后,现在可以通过 WorkManager 的 enqueue() 方法来调度它,将任务提交给系统进行管理和运行。

示例:

WorkManager.getInstance(applicationContext).enqueue(checkSystem)

三、WorkManager 进阶

3.1 设定任务请求

    前面介绍了如何创建简单的任务请求(WorkRequest)并将其放到系统队列中。在这个章节中,将会详细介绍任务和任务请求的各种设定。

3.1.1 任务约束

    可以通过向任务请求中添加 Constraints 指明任务运行的条件。Constraints通过 Constraints.Builder 创建,里面定义了设定不同约束的成员函数。例如电池非低电量、需要网络等等。

示例:

val constraints = Constraints.Builder()
    .setRequiresBatteryNotLow(true)
    .setRequiredNetworkType(NetworkType.CONNECTED) // 需要联网
    .build()

val checkSystem = OneTimeWorkRequestBuilder<CheckSystemWorker>()
    .setConstraints(constraints)
    .build()
WorkManager.getInstance(applicationContext).enqueue(checkSystem)

上面的示例中,为任务添加了需要网络连接的约束,在联网状态下,任务放入队列之后将会安排执行,但是如果当前设备断开了网络,并不会立刻执行,而是在队列中等到网络恢复,网络恢复后,等待的任务会继续执行。

    如果为一个任务设定了多个约束,当所有约束都满足时任务才会执行。如果运行期间任务的约束不满足,将会停止任务的执行,等到约束满足时,系统将会尝试恢复执行任务。

3.1.2 初始延迟

    如果任务没有设定约束或者所有约束都满足,任务有可能会立即执行。如果不想任务立即执行,可以设定一个最短的延迟时间。需要设定最短初始延迟时间,需要向任务请求中调用setInitialDelay()接口设定。

说明:为什么说最短延迟时间呢?因为将任务提交给系统,本来就有可能出现等待延迟,同理,设定了初始延迟时间的任务即使到了延迟时间,任务也有可能需要等待资源。换句话说,任务开始执行的时间 >= 设定的初始延迟时间

val checkSystem = OneTimeWorkRequestBuilder<CheckSystemWorker>()
    .setInitialDelay(3000, TimeUnit.MILLISECONDS)
    .build()
WorkManager.getInstance(applicationContext).enqueue(checkSystem)

上面的例子可以发现,当任务请求提交到系统时,并不会立即执行,而是等待一段时间才会开始执行。

3.1.3 重试和退避政策

    如果任务执行失败,需要让 WorkManager 重新尝试执行任务,可以从工作器(Worker)中返回 Result.retry(),然后,系统会根据退避延迟时间和政策重现调整调度任务。

    设置任务的退避政策通过调用 WorkerRequest.Builder 的 setBackoffCriteria()) 接口实现,设置的值包括退避延迟时间的增长方式重试任务最短等待时间重试等待时间的单位。退避延迟时间的增长方式在 BackoffPolicy 中定义,默认是 BackoffPolicy.EXPONENTIAL (指数增长)。

提示:退避延迟时间增长方式有两种

  • 线性增长(BackoffPolicy.LINEAR):线性函数增长,即 f ( n ) = t n f(n)=tn f(n)=tn,其中 t 为重试任务最短时间, n 为重试次数( n ≥ 1 n\geq1 n1)。
  • 指数正在(BackoffPolicy.EXPONENTIAL ):2的指数倍增长,即 f ( n ) = t ∗ 2 n f(n)=t*2^n f(n)=t2n,其中 t 为重试任务最短时间, n 为重试次数( n ≥ 1 n\geq1 n1)。
// 定义Worker
var times: Int = 0
var lastTime = 0L

class CheckDiskWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
    override fun doWork(): Result {
        Log.e("TEST", "Checking disk。。。。。。。。")
        if(times > 0) {
            Log.w("TEST", "Retry ${times}, Delay is ${(System.currentTimeMillis() - lastTime) / 1000} s")
        }
        Thread.sleep(3000)
        lastTime = System.currentTimeMillis()
        if(times < 5) {
            Log.e("TEST", "Checking disk failure.")
            times++
            // 需要重试,比如操作失败了,返回Result.retry()
            return Result.retry()
        }
        Log.e("TEST", "Checking disk done.")
        times = 0
        // 返回成功时,将不会再重试
        return Result.success()
    }
}
// 设定任务请求的重试和回避策略
val checkDisk = OneTimeWorkRequestBuilder<CheckDiskWorker>()
    .setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)
    .build()
WorkManager.getInstance(applicationContext).enqueue(checkDisk)

注意事项
1. OneTimeWorkRequest重试的最小时间间隔必须是OneTimeWorkRequest.MIN_BACKOFF_MILLISOneTimeWorkRequest.MAX_BACKOFF_MILLIS,超出这个范围的将会取临界值;
2. 因为任务提交给系统到真是执行并不是实时的,所以,真实的延长时间不一定是预期的(有可能会出现后一次重试时间间隔比前一次的还要短),但是一定是大于等于预期值。

3.1.4 定义任务的输入/输出

    将任务提交给系统,但是需要和任务进行交互,就需要实现任务的输入/输出。将需要的数据以参数的形式传入到任务(输入),任务执行完毕后返回结果(输出)。传入参数通过 WorkerReuqest 的 setInputData() 接口传入。输入和输出值以键值对的形式存储在 Data 对象中。在 Worker 中通过getInputData()接口获取输入参数。类似地,从 Worker 中返回数据(输出)也是通过 Data 对象,并通过Result 对应带有返回数据参数的方法返回结果,例如:Result.success(Data)Result.failure(Data)

说明:Data 对象可以通过原始的 Data.Builder 进行构建,也可以使用 worker 库提供的内联函数 workDataOf(vararg pairs: Pair) 进行构建,原理一样

示例:

// 定义Worker
class CheckNetworkWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
    override fun doWork(): Result {
        Log.e("TEST", "Checking network。。。。。。。。")
        Log.d("TEST", "Checking network, params......")
        for((key, value) in inputData.keyValueMap) {
            Log.d("TEST", "$key ---- $value")
        }
        Thread.sleep(3000)
        Log.e("TEST", "Checking network done.")
        return Result.success(Data.Builder().let {
            it.putInt("code", 200)
            it.putString("msg", "Network is fine")
            it.build()
        })
    }
}

// 构建WorkerRequest,并提交系统
val checkNetwork = OneTimeWorkRequestBuilder<CheckNetworkWorker>()
    .setInputData(Data.Builder().let {
        // Data以Builder的方式进行构建,传入的是键值对
        it.putString("operator", "Owen")
        it.putString("description", "Check network state")
        it.build()
    })
    .build()
WorkManager.getInstance(applicationContext).enqueue(checkNetwork)

注意:按照设计要求,Data 对象应该很小,大小上限为 10KB,值可以是字符串、基元类型或数组变体。如果需要将更多数据传入和传出工作器,应该将数据放在其他位置,例如 存储或者数据库。

    向任务输入数据已经知道如何实现了,可是在程序中如何获取任务输出的数据呢?主要有以下几步:

  1. 通过 WorkManagergetWorkInfoByXXXLiveData() 获取 WorkRequestLiveData 对象
  2. LiveData 对象添加观察者
  3. 在观察者的 onChanged() 方法中获取返回的数据
  4. WorkRequest 提交到系统

获取LiveData 对象的接口有多个,详细请参考:WorkManager,使用合适的方法获取,在后续章节也会详细介绍。

那么,以上的例子可以改为:

val checkNetwork = OneTimeWorkRequestBuilder<CheckNetworkWorker>()
    .setInputData(Data.Builder().let {
        // Data以Builder的方式进行构建,传入的是键值对
        it.putString("operator", "Owen")
        it.putString("description", "Check network state")
        it.build()
    })
    .build()

WorkManager.getInstance(applicationContext)
    .getWorkInfoByIdLiveData(checkNetwork.id)
    // observe方法是添加观察者,这个方法有两个参数,第一个参数是LifecycleOwner,可以传入
    .observe(this, object : Observer<WorkInfo> {
    override fun onChanged(t: WorkInfo?) {
        // 任务执行完毕之后,会在这里获取到返回的结果
        if(t?.state == WorkInfo.State.SUCCEEDED) {
            for((key, value) in t.outputData!!.keyValueMap) {
                Log.d("TEST", "Out Data $key ---- $value")
            }
        }
    }
})
WorkManager.getInstance(applicationContext).enqueue(checkNetwork)

参考资料:

  1. LifecycleOwner
  2. Observer

3.1.5 标记任务

    可以为任意 WorkRequest 对象分配标记字符串(tag),实现对任务按逻辑进行分组,这样就可以对使用特定标记的所有任务执行操作。例如,WorkManager.cancelAllWorkByTag(String) 会取消所有使用特定标记的任务,而 WorkManager.getWorkInfosByTagLiveData(String) 会返回具有该标记的所有任务的 LiveData 和状态列表。

    在构建 WorkRequest对象时,通过 WorkRequest.Builder.addTag(String) 向任务添加标记.

示例:

val checkNetwork = OneTimeWorkRequestBuilder<CheckNetworkWorker>()
    .setInputData(Data.Builder().let {
        // Data以Builder的方式进行构建,传入的是键值对
        it.putString("operator", "Owen")
        it.putString("description", "Check network state")
        it.build()
    })
    .addTag("networkWork")
    .build()

3.2 任务的状态及任务状态跟踪

3.2.1 任务的状态

    在任务的整个生命周期内,它会经历多个不同的状态(State)。下面将介绍这些状态:

  • 如果当前任务还有依赖的前提任务未完成,则当前任务处于 BLOCKED 状态
  • 如果当前任务能够在满足任务约束(Constraints)和时机条件好后立即运行,则任务被视为 ENQUEUED 状态
  • 如果任务正在执行时,则处于 RUNNING 状态
  • 如果任务执行完,在工作器中返回 Result.success() ,则任务被视为 SUCCEEDED 状态。这是一种终止状态,只有一次性任务 OneTimeWorkRequest 才可以进入这种状态。
  • 如果任务执行完,在工作器中返回 Result.failure() ,责任无被视为 FAILED 状态。这是一种终止状态,只有一次性任务 OneTimeWorkRequest 才可以进入这种状态。如果任务进入 FAILED 状态,则其所依赖的任务也会被标记为 FAILED ,并且不会运行。
  • 如果取消未完成的任务请求时,则任务会进入 CANCELLED 状态,这是一种终止状态,只有一次性任务 OneTimeWorkRequest 才可以进入这种状态。如果任务进入 CANCELLED 状态,则其所依赖的任务也会被标记为 CANCELLED ,并且不会运行。

这里介绍了各种状态,在后续会探讨如何跟踪这些状态

3.2.2 任务状态跟踪

    将工作任务加入队列后,可以通过 WorkManager 检查其状态。任务的相关信息在 WorkInfo 对象中提供,包括工作任务的 id、标签(tag)、当前状态(State) 和所有输出数据。

可以通过以下三种方式之一来获取 WorkInfo:

  • 如果需要针对特定的 WorkRequest,您可以通过 WorkRequest id,利用 WorkManager.getWorkInfoById(UUID) 或 WorkManager.getWorkInfoByIdLiveData(UUID) 接口来检索其 WorkInfo

  • 如果需要针对指定的标记,您可以利用 WorkManager.getWorkInfosByTag(String) 或 WorkManager.getWorkInfosByTagLiveData(String) 检索所有匹配的 WorkRequest 的 WorkInfo 对象。

  • 对于有唯一名称的工作任务,可以利用 WorkManager.getWorkInfosForUniqueWork(String) 或 WorkManager.getWorkInfosForUniqueWorkLiveData(String) 检索所有匹配的 WorkRequestWorkInfo 对象。

    利用以上各种方法获取到 LiveData 变量,然后通过注册监听器来观察 WorkInfo 的变化。

示例:

WorkManager.getInstance(applicationContext)
    .getWorkInfoByIdLiveData(checkNetwork.id)
    .observe(this, object : Observer<WorkInfo> {
    override fun onChanged(t: WorkInfo?) {
        // 任务执行完毕之后,会在这里获取到返回的结果
        Log.e("TEST", "WorkRequest state: ${t?.state}")
        if(t?.state == WorkInfo.State.SUCCEEDED) {
            // 显示任务完成通知提示
            Toast.makeText(this@MainActivity, "Check network success", Toast.LENGTH_LONG).show()
            for((key, value) in t.outputData!!.keyValueMap) {
                Log.d("TEST", "Out Data $key ---- $value")
            }
        }
    }
})
WorkManager.getInstance(applicationContext).enqueue(checkNetwork)

3.3 跟踪工作任务的中间状态

    从 WorkManager 2.3.0-alpha01 开始,为设置和观察工作器的中间进度添加了一些支持。如果应用在前台运行时,工作器保持运行状态,也可以使用返回 WorkInfo 的 LiveData 的 API 向用户显示此信息。

    ListenableWorker 现在支持 setProgressAsync() API,此类 API 可以保留中间进度。开发者能够借助这些 API实现通过界面观察工作任务的中间进度。进度由 Data 类型表示,这是一个可序列化的属性容器(类似于任务数据输入输出,并且受到相同的限制)。

    值得注意的是,只有在 ListenableWorker 运行时才能观察到和更新进度信息。如果尝试在 ListenableWorker 完成执行后在其中设置进度,则将会被忽略。可以通过WorkManagergetWorkInfoByXXX()getWorkInfoByXXXLiveData() 方法获取 WorkInfo 的实例,然后通过 getProgress() 方法获取包含进度信息的 Data 对象。

3.3.1 更新进度

    使用 ListenableWorker 或 Worker 调用 setProgressAsync() API 设置进度,会返回 ListenableFuture;更新进度是异步过程,因为更新过程包括将进度信息存储到数据库中。在 Kotlin 中,您可以使用 CoroutineWorker 对象的 setProgress() 扩展函数来更新进度信息。

示例:

// 定义Worker
class CheckDiskProgressWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {
    override fun doWork(): Result {
        Log.e("TEST", "Checking disk。。。。。。。。")
        Thread.sleep(1000)
        // 更新进度
        setProgressAsync(Data.Builder().let {
            it.putInt("progress", 20)
            it.build()
        })
        Thread.sleep(1000)
        // 更新进度
        setProgressAsync(Data.Builder().let {
            it.putInt("progress", 40)
            it.build()
        })
        Thread.sleep(1000)
        // 更新进度
        setProgressAsync(Data.Builder().let {
            it.putInt("progress", 60)
            it.build()
        })
        Thread.sleep(1000)
        // 更新进度
        setProgressAsync(Data.Builder().let {
            it.putInt("progress", 80)
            it.build()
        })
        Thread.sleep(1000)
        // 更新进度
        setProgressAsync(Data.Builder().let {
            it.putInt("progress", 100)
            it.build()
        })
        Log.e("TEST", "Checking disk done.")
        return Result.success(Data.Builder().let {
            it.putInt("code", 200)
            it.putString("msg", "Disk is fine")
            it.build()
        })
    }
}

提示:

  1. Worker类就是继承自ListenableWorker,所以工作任务类继承自Worker类,就拥有了设置中间进度的API。
  2. 进度的设置类似于任务数据输入输出,但是获取的接口不一样,使用getProgress()

3.3.2 跟踪任务进度

    跟踪进度信息也很简单。可以通过WorkManagergetWorkInfoByXXX()getWorkInfoByXXXLiveData() 方法获取 WorkInfo 的实例,然后通过 getProgress() 方法获取包含进度信息的 Data 对象。

示例:

val checkDisk = OneTimeWorkRequestBuilder<CheckDiskProgressWorker>()
    .build()

WorkManager.getInstance(applicationContext)
    .getWorkInfoByIdLiveData(checkDisk.id)
    .observe(this, object : Observer<WorkInfo> {
    override fun onChanged(t: WorkInfo?) {
        // 任务执行完毕之后,会在这里获取到返回的结果
        Log.e("TEST", "WorkRequest state: ${t?.state}")
        if(t?.state == WorkInfo.State.RUNNING) {
            Log.d("TEST", "Work progress --- ${t.progress.getInt("progress", 0)}")
            Toast.makeText(this@MainActivity, "Check disk... ${t.progress.getInt("progress", 0)}%", Toast.LENGTH_LONG).show()
        } else if(t?.state == WorkInfo.State.SUCCEEDED){
            Toast.makeText(this@MainActivity, "Check disk success", Toast.LENGTH_LONG).show()
        } else if(t?.state == WorkInfo.State.FAILED){
            Toast.makeText(this@MainActivity, "Check disk failed", Toast.LENGTH_LONG).show()
        }
    }
})
WorkManager.getInstance(applicationContext).enqueue(checkDisk)

注意:跟踪的观察者的 Lifecycle 如果终止,那么改观察者将不会接受到更新消息。

3.4 将多个工作任务链接在一起

    工作链用于将多个任务关联起来,并定义这些任务的运行顺序,当需要以特定的顺序运行多个任务时,工作链就非常有用。可以使用 WorkManager 创建工作链并对多个任务进行排队。

3.4.1 创建工作任务链

    创建工作链,主要有一下几步:

  1. 使用 WorkManager.beginWith(OneTimeWorkRequest) 或 WorkManager.beginWith(List) ,接口会返回 WorkContinuation 工作链实例;
  2. 通过返回的 WorkContinuation 对象使用 WorkContinuation.then(OneTimeWorkRequest) 或 WorkContinuation.then(List) API来添加从属的任务;
  3. 最后使用 WorkContinuation.enqueue() 方法将工作任务链提交到系统排队。

注意:
1. 每次调用 WorkContinuation.then(...) 都会返回一个新的 WorkContinuation 实例;
2. 通过带 List 类型参数的 API 添加的工作任务,可能会并行运行。

示例:

//定义Worker
class ContinuationCheck1(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {

    override fun doWork(): Result {
        Log.e("TEST", "ContinuationCheck1 .......")

        // 打印输入数据
        val strB = StringBuilder("ContinuationCheck1 输入数据:\n")
        for ((key, value) in inputData.keyValueMap) {
            strB.append(key)
                .append(" : ")
                .append(value)
                .append("\n")
        }
        Log.e("TEST", strB.toString())

        Thread.sleep(3000)

        Log.e("TEST", "ContinuationCheck1 done .......")

        return Result.success(workDataOf("whereFrom" to "This is from ContinuationCheck1"))
    }
}

class ContinuationCheck2(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {

    override fun doWork(): Result {
        Log.e("TEST", "ContinuationCheck2 .......")

        // 打印输入数据
        val strB = StringBuilder("ContinuationCheck2 输入数据:\n")
        for ((key, value) in inputData.keyValueMap) {
            strB.append(key)
                .append(" : ")
                .append(value)
                .append("\n")
        }
        Log.e("TEST", strB.toString())

        Thread.sleep(3000)

        Log.e("TEST", "ContinuationCheck2 done .......")

        return Result.success(workDataOf("whereFrom" to "This is from ContinuationCheck2"))
    }
}

class ContinuationCheck3(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {

    override fun doWork(): Result {
        Log.e("TEST", "ContinuationCheck3 .......")

        // 打印输入数据
        val strB = StringBuilder("ContinuationCheck3 输入数据:\n")
        for ((key, value) in inputData.keyValueMap) {
            strB.append(key)
                .append(" : ")
                .append(value)
                .append("\n")
        }
        Log.e("TEST", strB.toString())

        Thread.sleep(3000)

        Log.e("TEST", "ContinuationCheck3 done .......")

        return Result.success(workDataOf("whereFrom" to "This is from ContinuationCheck3"))
    }
}

class ContinuationCheck4(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {

    override fun doWork(): Result {
        Log.e("TEST", "ContinuationCheck4 .......")

        // 打印输入数据
        val strB = StringBuilder("ContinuationCheck4 输入数据:\n")
        for ((key, value) in inputData.keyValueMap) {
            strB.append(key)
                .append(" : ")
                .append(value)
                .append("\n")
        }
        Log.e("TEST", strB.toString())

        Thread.sleep(3000)

        Log.e("TEST", "ContinuationCheck4 done .......")

        return Result.success(workDataOf("whereFrom" to "This is from ContinuationCheck4"))
    }
}


// 创建工作链并提交到系统排队
WorkManager.getInstance(applicationContext)
    .beginWith(OneTimeWorkRequestBuilder<ContinuationCheck1>().also {
        it.setInputData(workDataOf("name" to "Owen", "position" to "Manager"))
    }.build())
    .then(OneTimeWorkRequestBuilder<ContinuationCheck2>().build())
    .then(listOf(OneTimeWorkRequestBuilder<ContinuationCheck3>().build(), OneTimeWorkRequestBuilder<ContinuationCheck4>().build()))
    .enqueue()

3.4.2 输入数据合并

    OneTimeWorkRequest 类型的工作链,父级的输出将会成为下一级的输入,所以在工作链中的输入存在两种情况:

  1. 只有一个父级 OneTimeWorkRequest :父级的输出即是下一级的输入,没有冲突。
  2. 包含多个父级 OneTimeWorkRequest :可以使用 WorkerManager 的 InputMerger 对输入参数进行合并(在构建 OneTimeWorkRequest 对象时通过 OneTimeWorkRequest.Builder.setInputMerger() API 进行设置),WorkerManager 提供了两种参数合并的方式:
  • OverwritingInputMerger:覆盖合并模式,系统会尝试将所有的键值对添加到输入数据中,如果有冲突(键一样),则会覆盖之前添加的键值;
  • ArrayCreatingInputMerger:创建数组合并模式,系统会将所有键值对添加到输入数据中,如果有冲突(键一样),则会将所有键一致的值放到一个新建的数组中,然后通过键添加到输入数据中。

示例:

// 定义Worker
class ContinuationCheck5(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {

    override fun doWork(): Result {
        Log.e("TEST", "ContinuationCheck5 .......")

        // 打印输入数据
        val strB = StringBuilder("ContinuationCheck5 输入数据:\n")
        for ((key, value) in inputData.keyValueMap) {

            strB.append(key)
                .append(" : ")
            // 使用了ArrayCreatingInputMerger,判断值是否为数组
            if (value is Array<*>) {
                strB.append("[")
                (value as Array<*>).forEach {
                    strB.append(it)
                        .append(",")
                }
                strB.also {
                    it.delete(it.lastIndexOf(",") - 1, it.length)
                }.append("]")
            } else {
                strB.append(value)
            }

            strB.append("\n")
        }
        Log.e("TEST", strB.toString())

        Thread.sleep(3000)

        Log.e("TEST", "ContinuationCheck5 done .......")

        return Result.success(workDataOf("whereFrom" to "This is from ContinuationCheck5"))
    }
}

// 创建工作链并提交到系统排队
WorkManager.getInstance(applicationContext)
    .beginWith(OneTimeWorkRequestBuilder<ContinuationCheck1>().also {
        it.setInputData(workDataOf("name" to "Owen", "position" to "Manager"))
    }.build())
    .then(OneTimeWorkRequestBuilder<ContinuationCheck2>().build())
    .then(listOf(OneTimeWorkRequestBuilder<ContinuationCheck3>().build(), OneTimeWorkRequestBuilder<ContinuationCheck4>().build()))
    .then(OneTimeWorkRequestBuilder<ContinuationCheck5>().also {
        // 设置输入数据合并模式
        it.setInputMerger(ArrayCreatingInputMerger::class)
    }.build())
    .enqueue()

注意:如果工作任务链中可能存在多个父级,输出可能存在冲突且使用了 ArrayCreatingInputMerger 合并模式的情况下,在下一级 OneTimeWorkRequest 读取参数时一定要判断值是否为数组

3.4.3 工作任务链的状态

     OneTimeWorkRequest 类型的工作链中,工作任务的状态需要注意以下几点:

  • 一个从属的 OneTimeWorkRequest 仅在其所有的父级 OneTimeWorkRequest 都完成并且成功(即返回 Result.success())时才会被解除阻塞(变为 ENQUEUED 状态)。

  • 如果有任何父级 OneTimeWorkRequest 失败(即返回 Result.failure()),则所有从属 OneTimeWorkRequest 也会被标记为 FAILED 且不会执行。

  • 如果有任何父级 OneTimeWorkRequest 被取消,则所有从属 OneTimeWorkRequest 也会被标记为 CANCELLED 且不会执行。

3.5 取消和停止工作任务

3.5.1 取消工作任务

    如果不再需要运行先前加入队列的作业,则可以申请取消。最简单的方法是使任务 id 并调用 WorkManager.cancelWorkById(UUID) 来取消单个 WorkRequest

示例:

WorkManager.getInstance(applicationContext).cancelWorkById(workId)

    在后台,WorkManager 会检查工作任务的状态(State),并做相应的处理:

  • 如果工作任务已经完成,则不会发生任何变化。否则,其状态将更改为 CANCELLED,之后就不会运行这个工作。任何依赖于这项工作任务(从属)的 WorkRequests 的状态也将变为 CANCELLED。

  • 如果工作当前的状态为 RUNNING,则工作器也会收到对 ListenableWorker.onStopped() 回调。重写此方法以处理所有可能需要的清理操作。

    您也可以使用 WorkManager.cancelAllWorkByTag(String) API 取消指定标记的所有工作任务。请注意,此方法会取消所有具有此标记的工作任务。此外,您还可以使用 WorkManager.cancelUniqueWork(String) 取消具有唯一名称的所有工作任务。

3.5.2 停止正在运行的工作器

    WorkManager 停止正在运行的工作器可能以下几种原因:

  • 您明确要求取消它(例如,通过调用 WorkManager.cancelWorkById(UUID) 取消)。
  • 如果是唯一工作 (使用 WorkManager.enqueueUniqueWork() API 提交的任务),并且使用了 ExistingWorkPolicy.REPLACE 策略添加新的 WorkRequest 到队列。旧的 WorkRequest 会立即被视为已终止。
  • 工作任务的约束已不再得到满足。
  • 系统出于某种原因指示您的应用停止工作。例如:如果工作任务超过 10 分钟的执行期限,可能会发生这种情况,系统会将工作安排在稍后重试。

    在上面这些情况下,WorkRequest 收到对 ListenableWorker.onStopped() 回调,在必要时,应该重写这个方法,执行清理工作并以协作方式完成工作器(例如,您应该在此时或者尽早关闭数据库和文件的打开句柄)。此外,可以通过调用 ListenableWorker.isStopped() 接口确认系统是否已经停止您的应用。另外需要注意的是,在调用了 onStopped() 后,即使通过返回 Result 来指示工作已完成,WorkManager 都会忽略该 Result,因为工作器已经被视为停止

注意:当停止一个正在运行的工作任务时,会回调 onStopped() ,如果在 doWork() 里面的任务有多个步骤,需要在每一个步骤完成处使用 isStopped (或者其他方法)判断是否停止执行下一步,否则仍旧会继续往下执行代码(这个类似Java中的 Thread),当然,除非你想在工作停止后继续执行所有代码,可以不进行处理。

3.6 重复性工作

    如果应用需要定期执行某些任务,例如,需要定期备份数据或者上传日志到服务器等。那么将考虑使用 PeriodicWorkRequest 实现定期任务。

示例:

val checkDisk = PeriodicWorkRequestBuilder<CheckDiskProgressWorker>(
                PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, TimeUnit.MILLISECONDS).build()

WorkManager.getInstance(applicationContext).enqueue(checkDisk!!)

注意事项
1. PeriodicWorkRequest最小重复执行时间间隔必须是PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS(900000毫秒,15分钟),小于这个值将取该临界值作为重复执行时间间隔;
2. PeriodicWorkRequestOneTimeWorkRequest 一样可以可以添加任务约束、跟踪任务状态、跟踪任务的中间状态以及取消和停止共工作等;
3. PeriodicWorkRequest无法使用链接功能,如果需要将服务链接起来,请使用 OneTimeWorkRequest;
4. 重复性工作在单次任务执行完毕后即返回了 Result.success() ,也不会监听到 State.SUCCEEDED 状态。

3.7 唯一工作任务

    唯一工作任务是一个概念性非常强的术语,它可确保一次只有一个具有特定名称的工作任务。与 id 不同的是,唯一名称是人类可读的,由开发者指定,而不是由 WorkManager 自动生成。也与标记不同,唯一名称仅与“一个”工作任务关联。

3.7.1 创建唯一工作任务

    创建为一个工作任务序列,可以通过调用 WorkManager.enqueueUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) 或 WorkManager.enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest) API 接口。第一个参数是唯一名称(这是我们用来标识 WorkRequest 的键);第二个参数是冲突解决策略(它指定了如果已经存在一个具有该唯一名称的未完成工作任务,WorkManager 应该如何处理);第三个参数是WorkRequest

示例:

class DoWorker(context: Context, workerParameters: WorkerParameters): Worker(context, workerParameters) {

    override fun doWork(): Result {

        val op = inputData.getString("op");

        Log.e("TEST", "$id $op .......")

        try {
            Thread.sleep(3000)
        } catch (e: InterruptedException) {
            onStopped()
        }
        if(isStopped) {
            Log.e("TEST", "$id $op stopped!")
            return Result.failure()
        }
        Log.e("TEST", "$id $op 50%.......")
        try {
            Thread.sleep(3000)
        } catch (e: InterruptedException) {
            onStopped()
        }
        if(isStopped) {
            Log.e("TEST", "$id $op stopped!")
            return Result.failure()
        }
        Log.e("TEST", "$id $op done .......")

        return Result.success()
    }

    override fun onStopped() {
        super.onStopped()
    }
}

// 添加唯一任务
WorkManager.getInstance(applicationContext).enqueueUniqueWork("checkDisk",
    ExistingWorkPolicy.REPLACE,
    OneTimeWorkRequestBuilder<DoWorker>()
        .setInputData(workDataOf("op" to "Check"))
        .build().also {
            Log.e("TEST", "${it.id} Check")
        })

唯一工作任务的冲突策略有以下几种:

  • ExistingWorkPolicy.REPLACE: 如果系统队列中存在有同样唯一名称的工作任务,取消并删除它,然后用新的工作任务替代;
  • ExistingWorkPolicy.KEEP: 如果系统队列中存在同样唯一名称的工作任务,保留原来的,忽略掉添加请求;
  • ExistingWorkPolicy.APPEND: 如果系统队列中存在同样唯一名称的工作任务,将新的工作任务添加到队列,等待队列中同名的任务完成之后,再执行新的工作任务。

注意: APPEND 策略不能和 PeriodicWorkRequest 一起使用,因为 PeriodicWorkRequest 不会自动进入完成状态。

    当程序存在不能多次排队的任务时,唯一工作就非常有用。例如,如果程序需要同步日志到服务器,可以添加一个名为“uploadLog”的唯一任务进行排队,并且使用 KEEP 策略,如果任务未完成再次添加这个唯一名称的任务,将会忽略后面添加的。

3.7.2 唯一工作链

    当要对一个工作任务链进行限制时,可以使用具有唯一名称的唯一名称任务链,使用 WorkManager.beginUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) 代替 WorkManager.beginWith() 即可,这样就可以采用唯一名称的策略来处理具有相同唯一名称的工作链了。例如:检查设备,需要检查设备的多个项目,每一项检查需要一定的时间来完成,将每一个检查项的执行写成一个Worker,添加到唯一工作链中,在工作链执行过程中,如果多次请求检查,可以通过策略 KEEP 来忽略掉后面的检查操作。

示例:

WorkManager.getInstance(applicationContext).beginUniqueWork("check",
    ExistingWorkPolicy.KEEP,
    OneTimeWorkRequestBuilder<DoWorker>()
        .setInputData(workDataOf("op" to "Check Disk"))
        .build())
    .then(OneTimeWorkRequestBuilder<DoWorker>()
        .setInputData(workDataOf("op" to "Check Network"))
        .build())
    .then(OneTimeWorkRequestBuilder<DoWorker>()
        .setInputData(workDataOf("op" to "Check System"))
        .build())
    .enqueue()

3.8 异步工作任务

    在 Workmanager 库中,提供了异步工作任务的支持 RxWorker,这个 Worker 的实现基于 RxJava;还有Kotlin专有的CoroutineWorker,这个 Worker 重写了 doWork 方法,便于执行挂起工作。

示例:

class CWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters) {
    override suspend fun doWork(): Result { // 不同之处是doWork方法多了个suspend关键字
        val op = inputData.getString("op");

        Log.e("TEST", "$id $op .......")

        delay(3000)

        if(isStopped) {
            Log.e("TEST", "$id $op stopped!")
            return Result.failure()
        }
        Log.e("TEST", "$id $op 50%.......")

        delay(3000)

        if(isStopped) {
            Log.e("TEST", "$id $op stopped!")
            return Result.failure()
        }
        Log.e("TEST", "$id $op done .......")
        return Result.success()
    }
}

四、WorkManager 高级概念

4.1 WorkManager配置和初始化

    默认情况下,当您的应用启动时,WorkManager 使用适合大多数应用的合理选项自动进行配置。如果您需要进一步自定义 WorkManager 管理和调度工作的方式,可以通过自己自定义 WorkManager 配置来初始化 WorkManager 。

4.1.1 WorkManager 2.1.0 及更高版本

    WorkManager 2.1.0 及更高版本有多种配置 WorkManager 的方式。其中最灵活方式是按需初始化。通过按需初始化,可以只在需要 WorkManager 时创建该组件,而不是每次启动应用时都创建。这样做可将 WorkManager 从关键启动路径中移除,从而提高应用启动性能。要使用按需初始化,请执行以下操作:

移除默认初始化程序

    要提供自己的配置,必须先移除默认的初始化程序。为此,请使用合并规则 tools:node="remove" 更新 AndroidManifest.xml

  1. AndroidManifest.xml 引入 tools
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.owen.workmanagerdm">
    
manifest>
  1. 添加 androidx.work.impl.WorkManagerInitializer provider 组件声明,并添加合并规则 tools:node="remove"
<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:exported="false"
    android:multiprocess="true"
    android:authorities="${applicationId}.workmanager-init"
    tools:node="remove"/>

说明: WorkManager aar库中的 AndroidManifest.xm 包含了初始化组件的声明,添加合并规则 tools:node="remove" 之后,在编译过程中,会将该组件删除。

实现 Configuration.Provider

  1. 自定义 Application 类,并实现 Configuration.Provider 接口,重写 getWorkManagerConfiguration() 方法,返回 Configuration 对象。

示例:

class MainApp : Application(), Configuration.Provider {


    override fun getWorkManagerConfiguration(): Configuration {
        return Configuration.Builder()
            .setMinimumLoggingLevel(android.util.Log.INFO)
            .setMaxSchedulerLimit(Configuration.MIN_SCHEDULER_LIMIT + 100)
            .build()
    }

}
  1. AndroidManifest.xml 中添加 application 声明,在 标签内添加 android:name 属性,并指向自定义的 Application.

  2. 当需要使用 WorkManager 时,调用方法 WorkManager.getInstance(Context)。WorkManager 将会调用应用的自定义的 getWorkManagerConfiguration() 方法来获取 Configuration。(无需手动调用 WorkManager.initialize()。)

4.1.2 WorkManager 2.0.1 及更早版本

    WorkManager 2.0.1 更早版本,有两个初始化选项:

默认初始化

    当应用启动时,WorkManager 使用自定义的 ContentProvider 进行初始化(此代码位于内部类 androidx.work.impl.WorkManagerInitializer 中)。默认初始化使用默认初始化程序(除非明确停用它,默认初始化程序适合大多数应用)和默认 Configuration 对 WorkManager 进行初始化。

自定义初始化

    如果想完全控制初始化程序,按以下步骤进行:

  1. 首先,停用默认初始化程序
  2. 然后,定义您自己的自定义配置。
val config = Configuration.Builder()
    .setMinimumLoggingLevel(android.util.Log.INFO)
    .setMaxSchedulerLimit(Configuration.MIN_SCHEDULER_LIMIT + 100)
    .build()
  1. 最后,调用 WorkManager.initialize()手动初始化 WorkManager
WorkManager.initialize(this, config)

更多关于配置项的说明参考: Configuration.Builder

注意:为了避免获取WorkManager单例时出现未初始化的情况,请确保初始化在 Application.onCreate()ContentProvider.onCreate() 中进行。

4.2 在 WorkManager 中进行线程处理

    在前面的内中,提到了 WorkManager 可以异步执行后台工作,这个实现可满足大多数应用的需求。

WorkManager 提供了四种不同类型的工作基元:

  • Worker 是最简单的实现,WorkManager 会在后台线程上自动运行它。

  • CoroutineWorker 是基于Kotlin协程实现的,建议 Kotlin 用户实现这个基类。CoroutineWorker 对后台工作公开挂起函数,在默认情况下它们运行默认的 Dispatcher,也可以对其进行自定义。

  • RxWorker 是基于 RxJava2 实现,如果项目中很多现有异步代码是用 RxJava 建模的,则应使用 RxWorker。与所有 RxJava2 概念一样,您可以自由选择所需的线程处理策略。

  • ListenableWorker 是 WorkerCoroutineWorkerRxWorker 的基类。该类专为需要与基于回调的异步 API(例如 FusedLocationProviderClient)进行交互并且不使用 RxJava2 的 Java 开发者而设计。

4.2.1 在工作器中进行线程处理

    当使用 Worker 时,WorkManager 会自动在后台线程中调用 Worker.doWork()。这个后台线程是来自于 WorkManager 的 Configuration 中指定的 Executor。默认情况下,WorkManager 会为您设置 Executor,但也可以通过手动初始化时自定义 Executor,详情请参考:WorkManager配置和初始化

class MainApp : Application(), Configuration.Provider {


    override fun getWorkManagerConfiguration(): Configuration {
        return Configuration.Builder()
            .setMinimumLoggingLevel(android.util.Log.INFO)
            .setMaxSchedulerLimit(Configuration.MIN_SCHEDULER_LIMIT + 100)
            .setExecutor(Executors.newFixedThreadPool(16)) // 使用自定义的Executor
            .build()
    }

}

下面是一个简单的工作器示例,检查设备状态:

class CWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters) {
    override suspend fun doWork(): Result {
        val op = inputData.getString("op");

        Log.e("TEST", "$id $op .......")

        delay(3000)

        Log.e("TEST", "$id $op 50%.......")

        delay(3000)

        Log.e("TEST", "$id $op done .......")
        return Result.success()
    }
}

    请注意,Worker.doWork() 是同步调用的,在 Worker.doWork() 中将会以阻塞方式完成整个后台工作,并在方法退出时完成工作。如果在 doWork() 中调用异步 API 并返回 Result,则回调可能无法正常运行。如果您遇到这种情况,请考虑使用 ListenableWorker(请参阅在 ListenableWorker 中进行线程处理)。

    正在运行的 Worker 停止时(无论是何种原因),工作器会收到对 Worker.onStopped() 的调用。重写此方法或在代码的检查点处调用 Worker.isStopped(),并在必要时释放资源。

示例:

class CWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters) {
    override suspend fun doWork(): Result {
        val op = inputData.getString("op");

        Log.e("TEST", "$id $op .......")

        delay(3000)

        if(isStopped) {
            Log.e("TEST", "$id $op stopped!")
            return Result.failure()
        }
        Log.e("TEST", "$id $op 50%.......")

        delay(3000)

        if(isStopped) {
            Log.e("TEST", "$id $op stopped!")
            return Result.failure()
        }
        Log.e("TEST", "$id $op done .......")
        return Result.success()
    }
}

注意:当 Worker 停止后,从 Worker.doWork() 返回什么已不重要了,因为 Result 将被忽略。

4.2.2 在 CoroutineWorker 中进行线程处理

    WorkManager 为 Kotlin 协程提供了非常完美的支持。如果使用 Kotlin 开发,请使用 work-runtime-ktx 依赖,定义工作器时不要扩展 Worker,而应扩展 CoroutineWorker,CoroutineWorker 使用的 API 有些不同。

    前面提到,doWork() 是同步运行的,这意味着内部的代码是按顺序执行,如果在内部实现了单项任务,这个也没有什么问题,但是如果里面有多项任务时,顺序执行无疑是增大了运行时间,如果将每一项任务放到协助中异步执行,将会节省很多时间,当然,前提是这些任务都是互不干扰,可以独立执行的,例如,检查多个文件的完整性,可以将对每个文件检查降到一个独立的协程中,这样就可以节省大量的检查时间。

示例:

class CheckFileWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters) {
    override suspend fun doWork(): Result = coroutineScope {
        val jobs = (0 until 20).map {
            async {
                checkFile(it)
            }
        }

        val results = jobs.awaitAll()

        results.forEach {
            if(!it) {
                Result.failure()
            }
        }

        Result.success()
    }

    private suspend fun checkFile(num: Int): Boolean {
        Log.e("TEST", "Checking file $num .......")

        delay(3000)

        if(isStopped) {
            Log.e("TEST", "Checking file $num stopped!")
            return false
        }
        Log.e("TEST", "Checking file $num 50%.......")
        delay(3000)

        if(isStopped) {
            Log.e("TEST", "Checking file $num stopped!")
            return false
        }
        Log.e("TEST", "Checking file $num 100% done .......")

        return true
    }
}

注意:CoroutineWorker.doWork() 是一个“挂起”函数。跟 Worker 不同的是, CoroutineWorker.doWork() 的码不会在 Configuration 中指定的 Executor 上运行,而是在默认的 Dispatchers.Default 上运行,也可以通过提供自己的 CoroutineContext 来自定义这个行为。

4.2.3 在 RxWorker 中进行线程处理

    WorkManager 提供了与 RxJava2 之间的互操作性。要开始使用这种互操作性,除了 work-runtime,还应添加 work-rxjava2 依赖。定义工作器时扩展 RxWorker,而不是扩展 Worker。最后替换 RxWorker.createWork() 方法以返回 Single用于表示您执行的 Result

示例:

public class RxCheckFileWorker extends RxWorker {

    public RxCheckFileWorker(Context context, WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Single<Result> createWork() {
        return Observable.range(0, 100)
            .flatMap { 
                checkFile(it)
            }
            .toList()
            .map { 
                Result.success() 
            };
    }
}

注意:RxWorker.createWork() 在主线程上调用,但默认情况下会在后台线程上订阅返回值。您可以替换 RxWorker.getBackgroundScheduler() 来更改订阅线程。另外,如果使用Kotlin开发,强烈建议使用 CoroutineWorker

     RxWorker 在停止时会妥善处理 Observer,因此,在停止工作器时无需做任何的特殊处理。

4.2.4 在 ListenableWorker 中进行线程处理

    在某些情况下,可能需要自定义线程处理策略。例如,需要处理基于回调的异步操作。在这种情况下,不能只依靠 Worker 来完成操作,因为它无法以阻塞方式完成这项任务。这时就需要通过 ListenableWorker 来实现了。ListenableWorker 是最低层级的工作器 API;WorkerCoroutineWorkerRxWorker 都是从这个类衍生而来的。ListenableWorker 只会发出应该开始和停止工作的信号,而线程处理则完全自定义编码实现。开始工作的信号是在主线程上调用,因此手动选择转到后台的线程也就只管重要。

    抽象方法 ListenableWorker.startWork() 会返回一个包含操作 ResultListenableFutureListenableFuture 是一个轻量级接口:它是一个 Future,用于提供设置监听器和传递异常信息的功能。在 startWork 方法中,必须返回一个 ListenableFuture,在操作完成后,需要将操作的 Result 设置到到这个 ListenableFuture

可以通过以下两种方式创建 ListenableFuture

  • 如果使用的是 Guava,请使用 ListeningExecutorService。
  • 否则,添加 councurrent-futures 依赖,然后使用 CallbackToFutureAdapter

示例:

class CheckFileListenableWorker(context: Context, workerParameters: WorkerParameters) : ListenableWorker(context, workerParameters) {

    override fun startWork(): ListenableFuture<Result> {
        return CallbackToFutureAdapter.getFuture(CallbackToFutureAdapter.Resolver {

            var succ = 0

            val callback = object : Callback {
                override fun onSuccess(id: Int) {
                    Log.e("TEST", "File $id check success")
                    succ++
                    if(succ == 8) {
                        it.set(Result.success())
                    }
                }

                override fun onFail(id: Int) {
                    Log.e("TEST", "File $id check failed")
                    it.set(Result.failure())
                }
            }

            val jobs = (0 until 8).map { index ->
                checkFileAsync(index, callback)
            }

            callback
        })
    }

    private fun checkFileAsync(num: Int, callback: Callback): Boolean {
        Thread(Runnable {
            Log.e("TEST", "Checking file $num .......")

            if(isStopped) {
                Log.e("TEST", "Checking file $num stopped!")
                return@Runnable
            }
            Log.e("TEST", "Checking file $num 50%.......")
            Thread.sleep(3000)

            if(isStopped) {
                Log.e("TEST", "Checking file $num stopped!")
                return@Runnable
            }
            Log.e("TEST", "Checking file $num 100% done .......")
            callback.onSuccess(num)
        }).start()

        return true
    }

    interface Callback {

        fun onSuccess(id: Int)

        fun onFail(id: Int)
    }
}

    如果工作停止,则始终会取消 ListenableWorkerListenableFuture。通过使用 CallbackToFutureAdapteraddCancellationListener() API 添加一个取消监听器,即可监听到 Work 被取消的事件。如果 Work 被取消,可以处理 Work 内部启动的异步线程,节省资源,而不是任由那些线程自生自灭。

示例:

class CheckFileListenableWorker(context: Context, workerParameters: WorkerParameters) : ListenableWorker(context, workerParameters) {

    override fun startWork(): ListenableFuture<Result> {
        return CallbackToFutureAdapter.getFuture(CallbackToFutureAdapter.Resolver {

            var succ = 0

            val callback = object : Callback {
                override fun onSuccess(id: Int) {
                    Log.e("TEST", "File $id check success")
                    succ++
                    if(succ == 8) {
                        it.set(Result.success())
                    }
                }

                override fun onFail(id: Int) {
                    Log.e("TEST", "File $id check failed")
                    it.set(Result.failure())
                }
            }

            it.addCancellationListener(object : Runnable {
                override fun run() {
                    Log.e("Test", "Worker was canceled!!!!!!!")
                    // TODO 在这里可以取消异步线程的运行
                }

            }, Executors.newFixedThreadPool(8))

            val jobs = (0 until 8).map { index ->
                checkFileAsync(index, callback)
            }

            callback
        })
    }

    private fun checkFileAsync(num: Int, callback: Callback): Boolean {
        Thread(Runnable {
            Log.e("TEST", "Checking file $num .......")

            if(isStopped) {
                Log.e("TEST", "Checking file $num stopped!")
                return@Runnable
            }
            Log.e("TEST", "Checking file $num 50%.......")
            Thread.sleep(3000)

            if(isStopped) {
                Log.e("TEST", "Checking file $num stopped!")
                return@Runnable
            }
            Log.e("TEST", "Checking file $num 100% done .......")
            callback.onSuccess(num)
        }).start()

        return true
    }

    interface Callback {

        fun onSuccess(id: Int)

        fun onFail(id: Int)
    }
}

编后语

本文篇幅有点长,是笔者在研究 WorkManager 过程中书写,如果有不正确之处请指教。另外,喜欢阅读官方文档的同学可以参考:WorkManager

本文测试Demo源码下载

你可能感兴趣的:(Android开发)