Android Jetpack ------ WorkManager 的使用

Posted 切切歆语

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Jetpack ------ WorkManager 的使用相关的知识,希望对你有一定的参考价值。

前言

大量应用程序都有在后台执行任务的需求。根据需求的不同,android为后台任务提供了多种解决方案,如JobScheduler,Loader,Service等。如果这些API没有被适当地使用,可能会消耗大量的电量。Android在解决应用程序耗电问题上做了各种尝试,从Doze到App Standby,通过各种方式限制和管理应用程序,以保证应用程序不会在后台过量消耗设备电量。WorkManager的出现,则是为应用程序中那些不需要及时完成的任务,提供统一的解决方案,以便在设备电量和用户体验之间达到一个比较好的平衡。

兼容范围广

WorkManager最低能兼容API Level 14,并且不需要你的设备安装有Google Play Services。因此,你不用过于担心兼容性问题,因为API Level 14已经能够兼容几乎100%的设备了。

 

WorkManager能依据设备的情况,选择不同的执行方案。在API Level 23+,通过JobScheduler来完成任务,而在API Level 23以下的设备中,通过AlarmManagerBroadcast Receivers组合完成任务。但无论采用哪种方案,任务最终都是交由Executor来完成。

WorkManager的两个重要特点

1.针对不需要及时完成的任务

比如,发送应用程序日志,同步应用程序数据,备份用户数据等。站在业务的角度,这些任务都不需要立即完成,如果我们自己来管理这些任务,逻辑可能会非常复杂,若API使用不恰当,可能会消耗大量电量。

2.保证任务一定会被执行

WorkManager能保证任务一定会被执行,即使你的应用程序当前不在运行中,哪怕你的设备重启,任务仍然会在适当的时候被执行。这是因为WorkManager有自己的数据库,关于任务的所有信息和数据都保存在这个数据库中,因此,只要你的任务交给了WorkManager,哪怕你的应用程序彻底退出,或者设备重新启动,WorkManager依然能够保证完成你交给的任务。

注意:WorkManager不是一种新的工作线程,它的出现不是为了替代其它类型的工作线程。工作线程通常立即运行,并在执行完成后给到用户反馈。而WorkManager不是即时的,它不能保证任务能立即得到执行。

在项目中使用WorkManager

1.在app的build.gradle中添加依赖。

dependencies {
    def versions = "2.6.0"
    implementation "androidx.work:work-runtime:$versions"
}

2.使用Worker定义任务 。

继承Worker类,覆盖doWork()方法,所有需要在任务中执行的代码都在该方法中编写。

public class UploadLogWorker extends Worker
{
    public UploadLogWorker(@NonNull Context context, @NonNull WorkerParameters workerParams)
    {
        super(context, workerParams);
    }

    /**
     * 耗时的任务,在doWork()方法中执行
     * */
    @NonNull
    @Override
    public Result doWork()
    {
        Log.e("UploadLogWorker", "doWork()");
        return Result.success();
    }
}

 

doWork()方法有3种类型的返回值

  • 若执行成功,则返回Result.success()
  • 若执行失败,则返回Result.failure().
  • 若需要重新执行,则返回Result.retry();

WorkRequest类介绍

WorkRequest是一个抽象类,它有两种实现方式:

    OneTimeWorkRequest: 一次性任务
    PeriodicWorkRequest: 周期性任务

WorkRequest进行对Work类的一种包装。我们可以通过WorkRequest来配置任务,配置任务就是告诉系统,任务何时运行及如何运行。


调度一次任务

对于无需额外配置的简单工作,我们可以使用静态方法from:

WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);

对于更复杂的任务,可以使用构建器

WorkRequest uploadWorkRequest =
   new OneTimeWorkRequest.Builder(MyWork.class)
       // 配置任务
       .build();

1.设置任务触发条件

例如: 我们可以设置当设备处于充电、网络已连接,且电池电量充足的状态下,才触发任务。

Constraints  constraints = new Constraints.Builder()
	.setRequiresChargint(true)
	.setRequiredNetWorkType(NetworkType.CONNECTED)
	.setRequiresBatteryNotLow(true)
	.build();

 将任务触发条件设置到WorkRequest.

2.设置延迟执行任务

可以通过setInitialDelay()方法,对任务进行延后执行

OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
				//任务 延时10s执行
				.setInitialDelay(10,TimeUnit.SECONDS)
                .build();

3.设置指数退避策略

假如Worker线程的执行出现了异常(比如任务执行失败),你希望一段时间后,重试该任务。 那么你可以在Worker的doWork()方法中返回Result.retry(), 系统会有默认的指数退避策略来帮你重试任务,也可以通过setBackoffCriteria()方法来自定义指数退避策略
 

OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
				//设置指数退避算法
				.setBackoffCriteria(BackoffPolicy.LINEAR,
				OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
				TimeUnit.MILLISECONDS)
                .build();

为任务设置Tag标签

设置tag标签后,我们就可以根据该标签追踪任务的状态,也可以用来取消任务
例如,WorkManager.cancelAllWorkByTag(String) 会取消带有特定标记的所有工作请求,WorkManager.getWorkInfosByTag(String) 会返回一个 WorkInfo 对象列表,该列表可用于确定当前工作状态。
 

OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
				//任务 延时10s执行
				.addTag("UploadTag")
                .build();

获取设置的标签和Id

 	/**
     * 获取 WorkRequest对应的tag
     */
    public @NonNull Set<String> getTags();
	/**
     * 获取 WorkRequest对应的UUID
     */
    public @NonNull UUID getId();

WorkManager介绍

将任务提交给系统

将任务配置好之后,需要将其提交给系统,WorkManager.enqueue()方法用于将配置好的WorkRequest交给系统来执行

WorkManager.getInstance(this).enqueue(uploadWorkRequest)

观察任务的状态

任务在提交给系统后,可以通过WorkInfo获取任务的状态。 WorkInfo包含任务的id、tag、Worker对象传递过来的outputData,以及任务当前的状态。
有三种方式可以得到WorkInfo对象

    WorkManager.getWorkInfosByTag() 根据WorkRequest设置的Tag标签
    WorkManager.getWorkInfoById() 根据Id获取
    WorkManager.getWorkInfosForUniqueWork()

如果希望实时获知任务的状态,可以获取对应的LiveData方法

    WorkManager.getWorkInfosByTagLiveData()
    WorkManager.getWorkInfoByIdLiveData()
    WorkManager.getWorkInfosForUniqueWorkLiveData()

通过LiveData,我们便可以在任务状态发送变化时收到通知
 

WorkManager.getInstance(this)
                .getWorkInfoByIdLiveData(uploadWorkRequest.getId())
                .observe(WorkManagerActivity.this, new Observer<WorkInfo>() {
                    @Override
                    public void onChanged(WorkInfo workInfo) {
                        }
                    }
                });

取消任务

与观察任务类似,我们也可以根据id或tag取消某个任务,或取消所有任务

//取消所有任务
WorkManager.getInstance(this).canceAllwork(); 
//根据Tag取消任务
WorkManager.getInstance(this).cancelAllWorkByTag()
//根据ID取消任务
WorkManager.getInstance(this).cancelWorkById()

WorkManager与Worker之间的参数传递

WorkRequest可以通过setInputData()方法向Worker传递数据。数据的传递通过Data对象来完成。需要注意的是,Data只能用于传递一些小的基本类型的数据,且数据大小不能超过10KB

Data inputData = new Data.Builder().putString("input_data","Hello World").build();

        OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
                .setInputData(inputData)
                .build();

Woker通过getInputData()方法接收数据,并在任务完成后,向WorkManager返回数据

@NonNull
    @Override
    //耗时的任务在doWork()方法中执行
    public Result doWork() {
        //接收外界传递进行的数据
        String inputData = getInputData().getString("input_data");

        //任务执行完成后返回数据
        Data outputData = new Data.Builder().putString("out_put_data","Task Success").build();
        return Result.success(outputData);
    }

WorkManager通过LiveData得到从Worker返回的数据

WorkManager.getInstance(this)
                .getWorkInfoByIdLiveData(uploadWorkRequest.getId())
                .observe(WorkManagerActivity.this, new Observer<WorkInfo>() {
                    @Override
                    public void onChanged(WorkInfo workInfo) {
                        if(workInfo!=null && workInfo.getState() == WorkInfo.State.SUCCEEDED){
                            String outputData = workInfo.getOutputData().getString("out_put_data");
                            textView.setText(outputData);
                        }
                    }
                });

        //将任务提交给系统执行
        WorkManager.getInstance(this).enqueue(uploadWorkRequest);

WorkInfo

WorkInfo 是Worker返回给我们的数据, WorkInfo包含id,tag,状态,返回值
我们可以通过WorkInfo.getState()获取当然任务执行的状态(State)
State有以下几种取值

    ENQUEUED: 任务刚入队列时的状态
    RUNNING: 任务正在执行时的状态
    SUCCEEDED: 任务执行成功时的状态
    FAILED : 任务执行失败时的状态
    BLOCKED: 工作链接时的状态
    CANCELLED:任务中途被取消时的状态

我们就可以通过State来判断当前任务的状态
 

// 获取到LiveData然后监听数据变化
        WorkManager.getInstance().getWorkInfoByIdLiveData(request.getId()).observe(this, new Observer<WorkInfo>() {
            @Override
            public void onChanged(@Nullable WorkInfo workInfo) {
                if (workStatus == null) {
                    return;
                }
                if (workInfo.getState() == WorkInfo.State.ENQUEUED) {
                    mTextOut.setText("任务入队");
                }
                if (workInfo.getState() == WorkInfo.State.RUNNING) {
                    mTextOut.setText("任务正在执行");
                }
                if (workInfo.getState() == WorkInfo.State.CANCELLED) {
                    mTextOut.setText("任务已经被取消了");
                }
                if (workInfo.getState() == WorkInfo.State.FAILED ) {
                    mTextOut.setText("任务执行失败了");
                }
                if (workInfo.getState() == WorkInfo.State.SUCCEEDED ) {
                    Data data = workStatus.getOutputData();
                    mTextOut.setText("任务完成" + "-结果:" + data.getString("key_name", "null"));
                }
               
            }
        });

1.一次性工作的状态
对于 one-time 工作请求,工作的初始状态为 ENQUEUED。
在 ENQUEUED 状态下,您的工作会在满足其 Constraints 和初始延迟计时要求后立即运行。接下来,该工作会转为 RUNNING 状态,然后可能会根据工作的结果转为 SUCCEEDED、FAILED 状态;或者,如果结果是 retry,它可能会回到 ENQUEUED 状态。在此过程中,随时都可以取消工作,取消后工作将进入 CANCELLED 状态。
 

 

SUCCEEDED、FAILED 和 CANCELLED 均表示此工作的终止状态。如果您的工作处于上述任何状态,WorkInfo.State.isFinished() 都将返回 true。

2.定期工作的状态
成功和失败状态仅适用于一次性工作和链式工作。定期工作只有一个终止状态 CANCELLED。这是因为定期工作永远不会结束。每次运行后,无论结果如何,系统都会重新对其进行调度

WorkManager基本使用

1.编写Work

 

public class UploadLogWorker extends Worker {
    public UploadLogWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }


    @NonNull
    @Override
    //耗时的任务在doWork()方法中执行
    public Result doWork() {
        //接收外界传递进行的数据
        String inputData = getInputData().getString("input_data");

        //任务执行完成后返回数据
        Data outputData = new Data.Builder().putString("out_put_data","Task Success").build();
        return Result.success(outputData);
    }

}

2.WorkManagerActivity

public class WorkManagerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_work_manager);
        final TextView textView = findViewById(R.id.tv_text);
        Data inputData = new Data.Builder().putString("input_data","Hello World").build();

        OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
                .setInputData(inputData)
                .build();

        WorkManager.getInstance(this)
                .getWorkInfoByIdLiveData(uploadWorkRequest.getId())
                .observe(WorkManagerActivity.this, new Observer<WorkInfo>() {
                    @Override
                    public void onChanged(WorkInfo workInfo) {
                        if(workInfo!=null && workInfo.getState() == WorkInfo.State.SUCCEEDED){
                            String outputData = workInfo.getOutputData().getString("out_put_data");
                            textView.setText(outputData);
                        }
                    }
                });

        //将任务提交给系统执行
        WorkManager.getInstance(this).enqueue(uploadWorkRequest);

    }
}

3.WorkManagerActivity布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".WorkManagerActivity"
    android:gravity="center">
    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Android"
        android:textSize="15sp"/>
  </LinearLayout>

调度周期性任务

前面提到过,WorkRequest有两种实现方式: OneTimeWorkRequest和PeriodicWorkRequest,分别对应一次性任务和周期性任务。一次性任务在任务成功执行后,便彻底结束。而周期性任务则会按照设定的时间定期执行。二者使用没有太大差别,需要注意的是,周期性任务的间隔时间不能少于15分钟
 

PeriodicWorkRequest saveRequest =
       new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
           //添加配置
           .build();

作的运行时间间隔定为一小时。

Constraints 工作约束

约束可确保将工作延迟到满足最佳条件时运行。有以下约束适用于WorkManager

NetWorkType:    约束运行工作所需的网络类型。例如 Wi-Fi (UNMETERED)。


BatteryNotLow:    如果设置为 true,那么当设备处于“电量不足模式”时,工作不会运行。


RequiresCharging:    如果设置为 true,那么工作只能在设备充电时运行。


DeviceIdle:    如果设置为 true,则要求用户的设备必须处于空闲状态,才能运行工作。如果您要运行批量操作,否则可能会降低用户设备上正在积极运行的其他应用的性能,建议您使用此约束。


StorageNotLow:    如果设置为 true,那么当用户设备上的存储空间不足时,工作不会运行。

例如,以下代码会构建了一个工作请求,该工作请求仅在用户设备正在充电且连接到 Wi-Fi 网络时才会运行:
 

Constraints constraints = new Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresCharging(true)
       .build();

WorkRequest myWorkRequest =
       new OneTimeWorkRequest.Builder(MyWork.class)
               .setConstraints(constraints)
               .build();

如果在工作运行时不再满足某个约束,WorkManager 将停止工作器。系统将在满足所有约束后重试工作。

总结

开发者经常需要处理后台任务,如果处理后台任务所采用的API没有被正确使用,那么很可能会消耗大量设备的电量。Android出于设备电量的考虑,为开发者提供了WorkManager,旨在将一些不需要及时完成的任务交给它来完成。虽然WorkManager宣称,能够保证任务得到执行,但我在真实设备中,发现应用程序彻底退出与重启设备,任务都没有再次执行。查阅了相关资料,发现这应该与系统有关系。我们前面也提到了,WorkManager会根据系统的版本,决定采用JobScheduler或是AlarmManager+Broadcast Receivers来完成任务。但是这些API很可能会受到OEM系统的影响。比如,假设某个系统不允许AlarmManager自动唤起,那么WorkManager很可能就无法正常使用。

而后我在模拟器中进行测试,模拟器采用的是Google原生系统,发现无论是彻底退出应用程序,或是重启设备,任务都能够被执行。所以,WorkManager在真实设备中不能正常使用,很可能就是系统的问题。因此,开发者在使用WorkManager作为解决方案时,一定要慎重。

另外,我还发现,周期任务的实际执行,与所设定的时间差别较大。执行时间看起来并没有太明显的规律。并且在任务执行完成后,WorkInfo并不会收到Success的通知。查阅了相关资料,发现Android认为Success和Failure都属于终止类的通知。意思是,如果发出这类通知,则表明任务彻底结束,而周期任务不会彻底终止,会一直执行下去,所以我们在使用LiveData观察周期任务时,不会收到Success这类的通知。这也是我们需要注意的地方。

以上是关于Android Jetpack ------ WorkManager 的使用的主要内容,如果未能解决你的问题,请参考以下文章

Android Jetpack架构组件带你了解Android Jetpack

Android Jetpack简介

Android Jetpack架构组件——什么是Jetpack?

Android Jetpack架构组件——什么是Jetpack?

《Android Jetpack从入门到精通+高级Jetpack强化实战》,最新Jetpack系列开发笔记开源

Android高级Jetpack架构组件+Jetpack compose强化实战