深圳幻海软件技术有限公司 欢迎您!

Android进阶之后台任务和定时服务,放弃AlarmManager全面拥抱WorkManager

2023-03-01

本文转载自微信公众号「Android开发编程」,作者Android开发编程。转载本文请联系Android开发编程公众号。前言WorkManager是google提供的异步执行任务的管理框架,会根据手机的API版本和应用程序的状态来选择适当的方式执行任务;当应用在运行的时候会在应用的进程中开一条线程来

本文转载自微信公众号「Android开发编程」,作者Android开发编程。转载本文请联系Android开发编程公众号。

前言

WorkManager是google提供的异步执行任务的管理框架,会根据手机的API版本和应用程序的状态来选择适当的方式执行任务;

当应用在运行的时候会在应用的进程中开一条线程来执行任务,当退出应用时,WorkManager会选择根据设备的API版本使用适合的算法调用JobScheduler或者Firebase JobDispatcher,或者AlarmManager来执行任务;

今天我们来介绍下;

一、WorkManager介绍

1、什么是WorkManager

  • WorkManager 是一个 API,使您可以轻松调度那些即使在退出应用或重启设备时仍应运行的可延期异步任务。WorkManager API 是一个针对先前的 Android 后台调度 API(包括 FirebaseJobDispatcher、GcmNetworkManager 和 JobScheduler)的合适的建议替换组件。WorkManager 在新版一致性 API 中整合了其前身的功能,该 API 支持 API 级别 14,同时可保证电池续航时间;
  • WorkManager 适用于可延期工作,即不需要立即运行但需要可靠运行的工作,即使用户退出或设备重启也不受影响。例如:向后端服务发送日志或分析数据 定期将应用数据与服务器同步;
  • WorkManager 不适用于应用进程结束时能够安全终止的运行中后台工作,也不适用于需要立即执行的工作;

2、优点

  • 向下兼容至api 14;
  • 可以添加任务执行的约束条件,比如说 延迟执行,是否在低电量模式下执行,是否在充电模式下执行,是否在设备空闲时执行等等;
  • 调度一次性或周期性异步任务;
  • 监管任务,可以随时取消任务;
  • 将任务链接起来,比如说执行可以指定多个任务的执行顺序;
  • 确保任务执行,即使应用或设备重启也同样执行任务;

二、WorkManager使用

1、添加依赖

//根据项目需要自行添加依赖,不需要全部添加 
    dependencies { 
      def work_version = "2.3.1" 
        // (Java only
        implementation "androidx.work:work-runtime:$work_version"//java 语言选这个 
        // Kotlin + coroutines 
        implementation "androidx.work:work-runtime-ktx:$work_version"//kotlin 选这个 
      } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

2、 创建一个后台任务

上传图片:

class UploadPicWork( 
    private val context: Context, 
    private val workerParameters: WorkerParameters 
) : 
    Worker(context, workerParameters) { 
    override fun doWork(): Result { 
        uploadPic()//具体上传图片的逻辑 
        return Result.success() 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

2.1 创建一个workrequest

//此处的 UploadPicWork 就是之前创建的任务 
val uploadPicWork = OneTimeWorkRequestBuilder<UploadPicWork>() 
                .setConstraints(triggerContentMaxDelay).build() 
  • 1.
  • 2.
  • 3.

2.2执行任务

//此处的 uploadPicWork 就是前一步创建的 workrequest 
  WorkManager.getInstance(myContext).enqueue(uploadPicWork) 
  • 1.
  • 2.

3、复杂的任务处理

3.1 创建任务执行的约束条件

//注意 以下条件都是 && 的关系 
val triggerContentMaxDelay = 
                Constraints.Builder() 
                .setRequiredNetworkType(NetworkType.CONNECTED)//网络链接的时候使用,避免各种网络判断,省时省力 
                .setRequiresDeviceIdle(false)//是否在设备空闲的时候执行 
                .setRequiresBatteryNotLow(true)//是否在低电量的时候执行 
                .setRequiresStorageNotLow(true)//是否在内存不足的时候执行 
                .setRequiresCharging(true)//是否时充电的时候执行 
                .setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS)//延迟执行 
                .build() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

3.2 为任务添加约束条件

val uploadPicWork = 
                OneTimeWorkRequestBuilder<UploadPicWork>() 
                    .setConstraints(triggerContentMaxDelay)//约束条件 
                    .build() 
 WorkManager.getInstance(myContext).enqueue(uploadPicWork)//执行 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

3.3为worker 传递参数

//可以采用这种方式传递参数 
   val UploadPicWork = 
                OneTimeWorkRequestBuilder<UploadPicWork>() 
                    //此处set input data 需要的参数 是一个Data对象,注意只可以添加一次,如果有多个参数需要传递,可以封装成一个data 数据类 
                    .setInputData(workDataOf("params_tag" to "params")) 
                    .setConstraints(triggerContentMaxDelay).build() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

3.4 获取参数

class UploadPicWork( 
    private val context: Context, 
    private val workerParameters: WorkerParameters 
) : 
    Worker(context, workerParameters) { 
    override fun doWork(): Result { 
       val params = inputData.getString("params_tag")//获取传递的参数 
        uploadPic()//上传图片 
        return Result.success() 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

4、Worker 的状态

在 doWork 函数中,我们返回的 Result.success(); 我们默认 ,任务 uploadPic 函数顺利的执行完成了,所以返回了 success 状态,但是在实际开发过程中 可以能因为各种各样的问题会导致 失败,这时候就不能返回success了;

Worker 的各种状态说明

  • 如果有尚未完成的前提性工作,则工作处于 BLOCKED State;
  • 如果工作能够在满足 约束条件 和时机条件后立即运行,则被视为处于 ENQUEUED 状态;
  • 当 Worker 在活跃地执行时,其处于 RUNNING State;
  • 如果 Worker 返回 Result.success(),则被视为处于 SUCCEEDED 状态。这是一种终止 State;只有 OneTimeWorkRequest 可以进入这种 State;
  • 如果 Worker 返回 Result.failure(),则被视为处于 FAILED 状态。这也是一个终止 State;只有 OneTimeWorkRequest 可以进入这种 State。所有依赖工作也会被标记为 FAILED,并且不会运行;
  • 当取消尚未终止的 WorkRequest 时,它会进入 CANCELLED State。所有依赖工作也会被标记为 CANCELLED,并且不会运行;

5、观察Worker 的状态

获取 WorkInfo

听过 id 获取,可以听过 WorkManager.getWorkInfoById(UUID) 或 WorkManager.getWorkInfoByIdLiveData(UUID) 来通过 WorkRequest id 来获取 WorkInfo;

WorkManager.getInstance(this) 
                .getWorkInfoByIdLiveData(UploadPicWork.id)// 通过id 获取 
                .observe(this, Observer { //it:WorkInfo 
                    it?.apply { 
                        when (this.state) { 
                            WorkInfo.State.BLOCKED -> println("BLOCKED"
                            WorkInfo.State.CANCELLED -> println("CANCELLED"
                            WorkInfo.State.RUNNING -> println("RUNNING"
                            WorkInfo.State.ENQUEUED -> println("ENQUEUED"
                            WorkInfo.State.FAILED -> println("FAILED"
                            WorkInfo.State.SUCCEEDED -> println("SUCCEEDED"
                            else -> println("else status ${this.state}"
                        } 
                    } 
                }) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

通过 tag 获取,可以利用 WorkManager.getWorkInfosByTag(String) 或 WorkManager.getWorkInfosByTagLiveData(String) 来通过 WorkRequest 的 WorkInfo 对象;

//要通过 tag 获取,则需要先设置 tag 
val UploadPicWork = 
                OneTimeWorkRequestBuilder<UploadPicWork>() 
                    .setInputData(workDataOf("params_tag" to "params"))//传递参数 
                    .setConstraints(triggerContentMaxDelay)//设置约束条件 
                    .addTag("tag")//设置tag 
                    .build() 
//获取 workInfo 
WorkManager.getInstance(this) 
                .getWorkInfosByTagLiveData("tag"
                .observe(this, Observer {it:List<WorkInfo>//此处返回的是一个集合,作为示例代码,默认只取 0 index 
                    it?.apply { 
                        when (this[0].state) { 
                            WorkInfo.State.BLOCKED -> println("BLOCKED"
                            WorkInfo.State.CANCELLED -> println("CANCELLED"
                            WorkInfo.State.RUNNING -> println("RUNNING"
                            WorkInfo.State.ENQUEUED -> println("ENQUEUED"
                            WorkInfo.State.FAILED -> println("FAILED"
                            WorkInfo.State.SUCCEEDED -> println("SUCCEEDED"
                            else -> println("else status ${this[0]}"
                        } 
                    } 
                }) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

6、多个Worker 的顺序执行

您可以使用 WorkManager 创建工作链并为其排队。工作链用于指定多个关联任务并定义这些任务的运行顺序。当您需要以特定的顺序运行多个任务时,这尤其有用;

6.1先后顺序执行单个任务

比如说有三个任务workA,workB,workC,并且执行顺序只能时workA---->workB---->workC可以用如下的方式处理;

WorkManager.getInstance() 
    .beginWith(workA) 
    .then(workB)  instance 
    .then(workC) 
    .enqueue(); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

上面的workA,workB,workC,都是WorkRequest的子类实现对象。WorkManager会根据上面的先后顺序来执行workA,workB,workC,,但是如果执行过程中三个任务中有一个失败,整个执行都会结束。并且返回Result.failure()

6.2先后顺序执行多个任务列

有时候可能要先执行一组任务,然后再执行下一组任务,可以使用下面的方式来完成。

WorkManager.getInstance() 
 
// First, run all the A tasks (in parallel): 
 
.beginWith(Arrays.asList(workA1, workA2, workA3)) 
 
// ...when all A tasks are finished, run the single B task: 
 
.then(workB) 
 
// ...then run the C tasks (in any order): 
 
.then(Arrays.asList(workC1, workC2)) 
 
.enqueue(); 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

7、 执行重复任务

就是在给定的时间间隔内定期执行任务,比如说 每个一个小时,上报位置信息,每个3个小时备份一个日志等等;

这个时间间隔不可低于15分钟;

val triggerContentMaxDelay = 
                Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED) 
//                    .setRequiresDeviceIdle(false
                    .setRequiresBatteryNotLow(true
                    .setRequiresStorageNotLow(true
                    .setRequiresCharging(true
                    .setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS) 
                    .build() 
//            val UploadPicWork = 
//                OneTimeWorkRequestBuilder<UploadPicWork>() 
//                    .setInputData(workDataOf("params_tag" to "params")) 
//                    .setConstraints(triggerContentMaxDelay) 
//                    .addTag("tag"
//                    .build() 
// 
            val build = PeriodicWorkRequestBuilder<UploadPicWork>( 
                1000 * 60 *15, 
                TimeUnit.MICROSECONDS 
            ).setConstraints(triggerContentMaxDelay).build() 
            WorkManager.getInstance(this).enqueue(build) 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

8、取消任务执行

通过任务的ID可以获取任务从而取消任务。任务ID可以从WorkRequest中获取;

cancelAllWork():取消所有任务;

cancelAllWorkByTag(String tag):取消一组带有相同标签的任务;

cancelUniqueWork( String uniqueWorkName):取消唯一任务;

UUID compressionWorkId = compressionWork.getId(); 
WorkManager.getInstance().cancelWorkById(compressionWorkId); 
  • 1.
  • 2.

注意并不是所有的任务都可以取消,当任务正在执行时是不能取消的,当然任务执行完成了,取消也是意义的,也就是说当任务加入到ManagerWork的队列中但是还没有执行时才可以取消;

9、使用WorkManager遇到的问题

9.1使用PeriodicWorkRequest只执行一次,并不重复执行

WorkManager instance= new PeriodicWorkRequest.Builder(PollingWorker.class, 10, TimeUnit.MINUTES) 
                .build(); 
  • 1.
  • 2.

原因:PeriodicWorkRequest默认的时间间隔是15分钟如果设置的时间小于15分钟,就会出现问题;

解决方法:设置的默认时间必须大于或等于15分钟。另外要注意,就算设置的时间为15分钟也不一定间隔15分钟就执行;

9.2在doWork()方法中更新UI导致崩溃

原因:doWork()方法是在WorkManager管理的后台线程中执行的,更新UI操作只能在主线程中进行

总结

 

在这个物欲横流人心浮躁的社会,我们一起学习加油共勉