Jetpack-WorkManager

WorkManager的主要特色

  • 向后兼容到API14
    • API 23以上使用JobScheduler
    • 在API 14~22之间使用BroadcastReceiver和AlarmManager的组合
  • 能够增长任务的约束,如网络或者充电状态
  • 能够调度一次性的或者周期性的异步任务
  • 能够监测和管理须要调度的任务
  • 能够把任务连接在一块儿
  • 保证任务执行,即便app或者设备被重启
  • 遵照节电功能如Doze模式

WorkManager是为了那些可延后执行的任务而设计,这些任务不须要当即执行,可是须要保证任务能被执行,即便应用退出或者设备重启。例如:java

  • 向后台服务发送日志或者分析
  • 周期性地与服务器同步数据

WorkManager不是为某些进程内的后台任务设计的,这些任务会在app进程退出时被中止,也不是那些须要当即执行的任务。android

使用WorkManager

声明依赖

dependencies {
  def work_version = "2.2.0"

    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"

    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:$work_version"

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

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

    // optional - Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"
  }
复制代码

建立后台任务

继承Worker,并重写doWork()数据库

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

    @NonNull
    @Override
    public Result doWork() {
        //business logic
        return Result.success();
    }
}
复制代码

Result返回结果有三种:api

  • 执行成功,Result.success()或Result.success(data)
  • 执行失败,Result.failure()或Result.failure(data)
  • 须要重试,Result.retry()

配置执行任务

Worker定义了具体的任务,WorkRequest定义了如何执行以及什么时候执行任务。若是是一次性的任务,能够用O呢TimeWorkRequest,若是是周期性的任务,可使用PeriodicWorkRequest。数组

OneTimeWorkRequest uploadReq = new OneTimeWorkRequest.Builder(UploadWorker.class).build();
复制代码
PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(UploadWorker.class, 10, TimeUnit.SECONDS).build();
复制代码

调度WorkRequest

调用WorkManager的enqueue方法bash

WorkManager.getInstance(ctx).enqueue(uploadReq);
复制代码

任务的具体执行时机依赖于WorkRequest设置的约束,以及系统的优化。服务器

定义WorkRequest

经过自定义WorkRequest能够解决如下场景:网络

  • 给任务增长约束条件,如网络状态
  • 保证任务执行的最低延迟时间
  • 处理任务的重试和补偿
  • 处理任务的输入和输出
  • 给一组任务设置标签

任务的约束

给任务增长约束,表示何时该任务能执行。多线程

例如,能够指定任务只有在设备空闲或者链接到电源时才能执行。app

Constraints constraints = new Constraints.Builder()
				//当本地的contenturi更新时,会触发任务执行(api需大于等于24,配合JobSchedule)
                .addContentUriTrigger(Uri.EMPTY, true)
    			//当content uri变动时,执行任务的最大延迟,配合JobSchedule
                .setTriggerContentMaxDelay(10, TimeUnit.SECONDS)
    			//当content uri更新时,执行任务的延迟(api>=26)
                .setTriggerContentUpdateDelay(100, TimeUnit.SECONDS)
    			//任务的网络状态:无网络要求,有网络链接,不限量网络,非移动网络,按流量计费的网络
                .setRequiredNetworkType(NetworkType.NOT_ROAMING)
    			//电量足够才能执行
                .setRequiresBatteryNotLow(true)
    			//充电时才能执行
                .setRequiresCharging(false)
    			//存储空间足够才能执行
    			.setRequiresStorageNotLow(false)
    			//设备空闲才能执行
                .setRequiresDeviceIdle(true)
                .build();
复制代码

当设置了多个约束,只有这些条件都知足时,任务才会执行。

当任务在运行时,若是约束条件不知足,WorkManager会终止任务。这些任务会在下一次约束条件知足时重试。

延迟初始化

若是任务没有约束或者约束条件知足时,系统可能会马上执行这些任务。若是不但愿任务当即执行,能够指定这些任务延迟必定时间再执行。

OneTimeWorkRequest uploadReq = new OneTimeWorkRequest.Builder(UploadWorker.class).setInitialDelay(10, TimeUnit.SECONDS).build();
复制代码

重试和补偿策略

若是须要WorkManager重试任务,可让任务返回Result.retry()。

任务会被从新调度,而且会有一个默认的补偿延迟和策略。补偿延迟指定了任务被重试的一个最小的等待时间。补充策略定义了补偿延迟在接下来的几回重试中会如何增长。默认是指数增长的。

OneTimeWorkRequest uploadReq = new OneTimeWorkRequest.Builder(UploadWorker.class).setInitialDelay(10, TimeUnit.SECONDS)
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10 ,TimeUnit.SECONDS)
                .build();
复制代码

定义输入和输出

任务可能须要传入数据做为输入参数或者返回结果数据。例如,一个上传图片的任务须要图片的URI,可能也须要图片上传后的地址。

输入和输出的值以键-值对的形式存储在Data对象中。

Data data = new Data.Builder().putString("key1", "a").build();
OneTimeWorkRequest uploadReq = new OneTimeWorkRequest.Builder(UploadWorker.class)
                .setInputData(data)
                .build();
复制代码

Wroker类调用Worker.getInputData()来获取输入参数。

Data类也能够做为输出。在Worker中返回Data对象,经过调用Result.success(data)或Result.failure(data)。

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

    @NonNull
    @Override
    public Result doWork() {
        //business logic
        Data data = new Data.Builder().putString("image-url","http://xxxx.png").build();
        return Result.success(data);
    }
}
复制代码

标记任务

对任何的WorkRequest对象,经过给一组任务赋值一个标签就能够在逻辑上把它们变成一个组。这样就能够操做特定标签的所有任务。

例如,WorkManager.cancelAllWorkByTag(String)取消了全部该标签的任务;WorkManager.getWorkInfosByTagLiveData(String)返回了一个LiveData包含了该标签下的所有任务的状态列表

OneTimeWorkRequest uploadReq = new OneTimeWorkRequest.Builder(UploadWorker.class)
                .addTag("upload")
                .build();
复制代码

任务的状态和观察任务

任务状态

在任务的生命周期中,会通过各类状态:

  • BLOCKED,当任务的先决条件还未知足时,任务处于阻塞状态
  • ENQUEUED,当任务的约束条件和时间知足可以执行时,处于入队状态
  • RUNNING,当任务正在被执行
  • SUCCEEDED,一个任务返回Result.success(),就处于成功状态。这个是终点状态;只有一次性的任务(OneTimeWorkRequest)能到达这个状态
  • FAILED,一个任务返回Result.failure(),就处于失败状态。这也是一个终点状态;只有一次性的任务(OneTimeWorkRequest)能到达这个状态。全部依赖它的任务都被会标记为FAILED而且不会被执行
  • CANCELLED,当显式地取消一个没有终止的WorkRequest,会处于取消状态。全部依赖它的任务也会被标记为CANCELLED,而且不会执行

观察任务

当把任务放入队列中,WorkManager容许检查它们的状态。这些信息能够经过WorkInfo对象获取,包含了任务的id,tag,当前的State和输出的数据。

有如下几个方法获取WorkInfo:

  • 对特定的WorkRequest,能够经过id获取它的WorkInfo,调用WorkManager.getWorkInfoById(id)或WorkManager.getWorkInfoByIdLiveData(id)
  • 对一个给定的tag,能够获取全部匹配这个tag的任务们的WorkInfo对象,调用WorkManager.getWorkInfosByTag(tag)或WorkManager.getWorkInfosByTagLiveData(tag)
  • 对一个独特的任务的名称,能够获取全部符合的任务的WorkInfo对象,调用WorkManager.getWorkInfosForUniqueWork(name)或WorkManager.getWorkInfosForUniqueWorkLiveData(name)

上述方法返回的LiveData能够经过注册一个监听器观察WorkInfo的变化。

WorkInfo workInfo = WorkManager.getInstance(this).getWorkInfoById(UUID.fromString("uuid")).get();
        WorkManager.getInstance(this).getWorkInfoByIdLiveData(UUID.fromString("uuid")).observe(this, new Observer<WorkInfo>() {
            @Override
            public void onChanged(WorkInfo workInfo) {
                
            }
        });
复制代码

观察任务的进度

2.3.0-alpha01版本的WorkManager增长了设置和观察任务的进度的支持。若是应用在前台时任务在运行,进度信息能够展现给用户,经过API返回的WorkInfo的LiveData。

ListenableWorker如今支持setProgressAsync(),可以保存中间进度。这些API使得开发者可以设置进度,以便在UI上可以展现出来。进度用Data类型表示,这是一个可序列化的属性容器(相似输入和输出,受一样的限制)。

进度信息只有在ListenableWorker运行时才能被观察和更新。当ListenableWorker结束时设置进度会被忽略。经过调用getWorkInfoBy..()或者getWorkInfoBy...LiveData()接口来观察进度信息。这些方法能返回WorkInfo的对象实例,它们有一个新的getProgress()方法能返回Data对象。

更新进度

开发者使用ListenableWorker或者Worker,setProgressAsync()接口会返回一个ListenableFuture;更新进度是异步的,包含了存储进度信息到数据库。在Kotlin中,可使用CoroutineWorker对象的setProgress()扩展方法来更新进度信息。

public class ProgressWorker extends Worker {
    public ProgressWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
        setProgressAsync(new Data.Builder().putInt("progress", 0).build());
    }

    @NonNull
    @Override
    public Result doWork() {
        setProgressAsync(new Data.Builder().putInt("progress", 100).build());
        return Result.success();
    }
}
复制代码

观察进度

观察进度信息比较简单。可使用getWorkInfoBy...()或getWorkInfoBy...LiveData()方法,获取一个WorkInfo的引用。

WorkRequest progress = new OneTimeWorkRequest.Builder(ProgressWorker.class).addTag("progress").build();
        WorkManager.getInstance(this).getWorkInfoByIdLiveData(progress.getId()).observe(this, new Observer<WorkInfo>() {
            @Override
            public void onChanged(WorkInfo workInfo) {
                int progress = workInfo.getProgress().getInt("progress", 0);
            }
        });
复制代码

连接工做

简介

WorkManager容许建立和入队一连串的任务,能够指定多个依赖的任务,以及它们的执行顺序。若是要以一个特定的顺序执行多个任务时会很是有用。

要建立一连串的任务,可使用WorkManager.beginWith(OneTimeWorkRequest)或者WorkManager.beginWith(List),它们会返回一个WorkContinuation实例。

一个WorkContinuation实例以后能够用来添加依赖的OneTimeWorkRequest,经过调用WorkContainuation.then(OneTimeWorkRequest)或WorkContinuation.then(List)。

每一个WorkContinuation.then(...)的调用,会返回一个新的WorkContinuation实例。若是添加了OneTimeRequest的列表,这些请求有可能会串行地运行。

最终,能够用WorkContinuation.enqueue()方法把WorkContinuation链放入队列。

WorkManager.getInstance(myContext)
    // Candidates to run in parallel
    .beginWith(Arrays.asList(filter1, filter2, filter3))
    // Dependent work (only runs after all previous work in chain)
    .then(compress)
    .then(upload)
    // Don't forget to enqueue()
    .enqueue();
复制代码

输入合并

当使用链式的OneTimeWorkRequest,父OneTimeWorkRequest的输出会做为子任务的输入。因此上例中的filter1,filter2和filter3的输出会做为compress任务的输入。

为了管理来自多个父任务的输入,WorkManager使用InputMerger进行输入合并。

WorkManager提供了两种不一样类型的InputMerger:

  • OverwritingInputMerger试图把全部输入中的键添加到输出中。当键冲突时,会覆盖以前的键。
  • ArrayCreatingInputMerger在必要时会试图合并全部输入,放入数组中。
OneTimeWorkRequest compress =
    new OneTimeWorkRequest.Builder(CompressWorker.class)
        .setInputMerger(ArrayCreatingInputMerger.class)
        .setConstraints(constraints)
        .build();
复制代码

连接和任务的状态

当建立一个OneTimeWorkRequest任务链时,有几件事要记住:

  • 当全部父OneTimeWorkRequest成功执行时,子OneTimeWorkRequest才会是非阻塞的(过渡到ENQUEUED状态)。
  • 当任何一个父OneTimeWorkRequest执行失败,全部依赖于它的OneTimeWorkRequest都是被标记为FAILED。
  • 当任何一个父OneTimeWorkRequest被取消,全部依赖于它的OneTimeWorkRequest都会被标记为CANCELED。

取消和终止任务

若是再也不须要入队的任务执行,能够取消它。取消一个单独的WorkRequest最简单的方法是使用id并调用WorkManager.cancenWorkById(UUID)。

WorkManager.cancelWorkById(workRequest.getId());
复制代码

在底层,WorkManager会检查任务的状态。若是这个任务已经完成,没有任何事情发生。不然,这个任务的状态会转移到CANCELED 而且这个任务之后不会再运行。任何依赖这个任务的其余WorkRequest都会被标记为CANCELED。

另外,若是当前任务正在运行,这个任务会触发ListenableWorker.onStopped()的回调。重写这个方法来处理任何可能的清理工做。

也能够用标签来取消任务,经过调用WorkManager.cancelAllWorkByTag(String)。注意,这个方法会取消全部有这个标签的任务。另外,也能够调用WorkManager.cancelUniqueWork(String)取消带有该独特名字的所有任务。

终止一个运行中的任务

有几种状况,运行中的任务会被WorkManager终止:

  • 显式地调用了取消任务的方法
  • 任务的约束条件不再会知足
  • 系统由于某些缘由终止了应用。若是超过了执行的最后时间10分钟以上就有可能发生。这个任务以后会被调度进行重试。

在这些状况下,任务会触发ListenableWorker.onStopped()的回调。你应该执行任务清理和配合地终止任务,以防系统会关闭应用。好比,在此时应该关闭开启的数据库和文件句柄,或者在更早的时间里作这些事情。另外,不管什么时候想要判断任务是否被终止了能够查询ListenableWorker.isStopped()。即便您经过在调用onStopped()以后返回一个结果来表示您的工做已经完成,WorkManager也会忽略这个结果,由于这个任务已经被认为是结束了。

循环任务

你的应用优点会须要周期性地运行某些任务。好比,应用可能会周期性地备份数据,下载新的数据,或者上传到日志到服务器。

使用PeriodicWorkRequest来执行那些须要周期性地运行的任务。

PeriodicWorkRequest不能被连接。若是任务须要连接,考虑使用OneTimeWorkRequest。

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

PeriodicWorkRequest saveRequest =
        new PeriodicWorkRequest.Builder(SaveImageFileWorker.class, 1, TimeUnit.HOURS)
                  .setConstraints(constraints)
                  .build();

WorkManager.getInstance(myContext)
    .enqueue(saveRequest);
复制代码

周期间隔是两次重复执行的最小时间。任务实际执行的时间取决于任务设置的约束和系统的优化。

观察PeriodicWorkRequest的状态的方法跟OneTimeWorkRequest同样。

惟一任务

惟一任务是一个有用的概念,它保证了某一时刻只能有一个带有特定名称的任务链。不像id是由WorkManager自动生成的,惟一名称是可读的,而且是开发者指定的。也不像tag,惟一名称只能跟一个任务链关联。

能够经过调用WorkManager.enqueueUniqueWork()或者WorkManager.enqueueUniqueWork()来建立一个惟一任务队列。第一个参数是惟一名字—用于识别WorkRequest。第二个参数是冲突的解决策略,指定了若是已经存在一个未完成的同名任务链时WorkManager采起的措施:

  • REPLACE:取消已经存在的任务链,并用新的取代;
  • KEEP:保持已有的任务,并放弃新的任务请求;
  • APPEND:把新的任务放在已有的任务后,当已有的任务完成后再执行新加入的第一个任务。对于PeriodicWorkRequest,不能用APPEND策略。

若是有一个任务不须要屡次放入队列时,惟一任务会颇有用。例如,若是你的应用须要同步数据到网络,能够入队一个命名为“sync”的事件,而且若是已经有这个名字的任务了,那么新的任务应该被忽略。若是你须要逐渐地创建一个很长的任务链,惟一任务队列也颇有用。例如,一个相片编辑应用可能会让用户撤销一长串编辑动做。每一个撤销操做可能会耗时一段时间,可是它们必须按正确的顺序执行。在这个状况下,这个应用能够建立一个“undo”的任务链,并把每一个新的操做放在最后。

若是要建立一个惟一任务链,可使用WorkManager.beginUniqueWork()而不是beginWith()。

测试

介绍和设置

WorkManager提供了work-test工件在Android设备上为任务进行单元测试。

为了使用work-test工件,须要在build.gradle中添加androidTestImplementation依赖。

androidTestImplementation "androidx.work:work-testing:2.3.0-alpha01"
复制代码

概念

work-testing提供了一个测试模式下的WorkManager的特殊实现,它是用WorkManagerTestInitHelper进行初始化。

work-testing工件提供了一个SynchronousExecutor使得能更简单地用同步方式进行测试,不须要去处理多线程,锁或占用。

在build.gradle中编辑依赖

testImplementation 'junit:junit:4.12'
 androidTestImplementation 'androidx.test:runner:1.2.0'
 androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
 androidTestImplementation 'androidx.test.ext:junit:1.1.1'
 androidTestImplementation "androidx.work:work-testing:2.2.0"
复制代码

单元测试类setup

@Before
public void setup() {
	Context context = ApplicationProvider.getApplicationContext();
    Configuration config = new Configuration.Builder()
    // Set log level to Log.DEBUG to
    // make it easier to see why tests failed
    	.setMinimumLoggingLevel(Log.DEBUG)
        // Use a SynchronousExecutor to make it easier to write tests
        .setExecutor(new SynchronousExecutor())
        .build();

    // Initialize WorkManager for instrumentation tests.
    WorkManagerTestInitHelper.initializeTestWorkManager(context, config);
}
复制代码

构建测试

WorkManager在测试模式下已经初始化,能够开始测试任务。

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

    @NonNull
    @Override
    public Result doWork() {
        Data input = getInputData();
        if (input.size() == 0) {
            return Result.failure();
        } else {
            return Result.success(input);
        }
    }
}
复制代码

基础测试

测试模式下的使用跟正常应用中使用十分相似。

package com.example.hero.workmgr;

import android.content.Context;
import android.util.Log;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.work.Configuration;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import androidx.work.testing.SynchronousExecutor;
import androidx.work.testing.WorkManagerTestInitHelper;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

/** * Instrumented test, which will execute on an Android device. * * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Before
    public void setup() {
        Context context = ApplicationProvider.getApplicationContext();
        Configuration config = new Configuration.Builder()
                // Set log level to Log.DEBUG to
                // make it easier to see why tests failed
                .setMinimumLoggingLevel(Log.DEBUG)
                // Use a SynchronousExecutor to make it easier to write tests
                .setExecutor(new SynchronousExecutor())
                .build();

        // Initialize WorkManager for instrumentation tests.
        WorkManagerTestInitHelper.initializeTestWorkManager(context, config);
    }

    @Test
    public void testWorker() throws Exception {
        Data input = new Data.Builder().put("a", 1).put("b", 2).build();

        OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(TestWorker.class).setInputData(input).build();

        WorkManager mgr = WorkManager.getInstance(ApplicationProvider.getApplicationContext());
        mgr.enqueue(request).getResult().get();
		//该接口其实获得的是一个StatusRunnable,从数据库中查询到WorkInfo后会调用SettableFuture.set(),而后get()会返回对应的WorkInfo
        WorkInfo workInfo = mgr.getWorkInfoById(request.getId()).get();
        assertThat(workInfo.getState(), is(WorkInfo.State.ENQUEUED));
        workInfo = mgr.getWorkInfoById(request.getId()).get();
        assertThat(workInfo.getState(), is(WorkInfo.State.RUNNING));
        workInfo = mgr.getWorkInfoById(request.getId()).get();
        assertThat(workInfo.getState(), is(WorkInfo.State.SUCCEEDED));
        Data output = workInfo.getOutputData();
        assertThat(output.getInt("a", -1), is(1));
    }
}
复制代码

模拟约束,延迟和循环任务

WorkManagerTestInitHelper提供一个TestDriver实例,它可以模拟初始化延迟,ListenableWorker须要的约束条件和循环任务的周期等。

测试初始化延迟

任务能够设置初始化延迟。用TestDriver设置任务所须要的初始化延迟,就不须要等待这个时间到来,这样能够测试任务的延迟是否有效。

@Test
public void testDelay() throws Exception {
    Data input = new Data.Builder().put("a", 1).put("b", 2).build();

    OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(TestWorker.class).setInputData(input).setInitialDelay(10, TimeUnit.SECONDS).build();
    WorkManager mgr = WorkManager.getInstance(ApplicationProvider.getApplicationContext());

    TestDriver driver = WorkManagerTestInitHelper.getTestDriver(ApplicationProvider.getApplicationContext());
    mgr.enqueue(request).getResult().get();

    driver.setInitialDelayMet(request.getId());
    WorkInfo workInfo = mgr.getWorkInfoById(request.getId()).get();
    assertThat(workInfo.getState(), is(WorkInfo.State.ENQUEUED));
    workInfo = mgr.getWorkInfoById(request.getId()).get();
    assertThat(workInfo.getState(), is(WorkInfo.State.RUNNING));
    workInfo = mgr.getWorkInfoById(request.getId()).get();
    assertThat(workInfo.getState(), is(WorkInfo.State.SUCCEEDED));
}
复制代码
测试约束

TestDriver能够调用setAllConstraintsMet设置全部的约束都知足条件。

@Test
public void testConstraint() throws Exception {
    Data input = new Data.Builder().put("a", 1).put("b", 2).build();

    Constraints constraints = new Constraints.Builder().setRequiresDeviceIdle(true).build();
    OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(TestWorker.class).setInputData(input).setConstraints(constraints).build();
    WorkManager mgr = WorkManager.getInstance(ApplicationProvider.getApplicationContext());

    TestDriver driver = WorkManagerTestInitHelper.getTestDriver(ApplicationProvider.getApplicationContext());
        mgr.enqueue(request).getResult().get();

    driver.setAllConstraintsMet(request.getId());
    WorkInfo workInfo = mgr.getWorkInfoById(request.getId()).get();
    assertThat(workInfo.getState(), is(WorkInfo.State.ENQUEUED));
    workInfo = mgr.getWorkInfoById(request.getId()).get();
    assertThat(workInfo.getState(), is(WorkInfo.State.RUNNING));
    workInfo = mgr.getWorkInfoById(request.getId()).get();
    assertThat(workInfo.getState(), is(WorkInfo.State.SUCCEEDED));
}
复制代码
测试循环任务

TestDriver提供了一个setPeriodDelayMet来表示间隔已经达到。

@Test
public void testPeriod() throws Exception {
    Data input = new Data.Builder().put("a", 1).put("b", 2).build();

    PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(TestWorker.class, 10, TimeUnit.SECONDS).setInputData(input).build();
    WorkManager mgr = WorkManager.getInstance(ApplicationProvider.getApplicationContext());

    TestDriver driver = WorkManagerTestInitHelper.getTestDriver(ApplicationProvider.getApplicationContext());
    mgr.enqueue(request).getResult().get();

    driver.setPeriodDelayMet(request.getId());
    WorkInfo workInfo = mgr.getWorkInfoById(request.getId()).get();
    assertThat(workInfo.getState(), is(WorkInfo.State.ENQUEUED));
    workInfo = mgr.getWorkInfoById(request.getId()).get();
    assertThat(workInfo.getState(), is(WorkInfo.State.RUNNING));
    workInfo = mgr.getWorkInfoById(request.getId()).get();
    //循环任务完成后,状态仍会变成ENQUEUED(WorkerWrapper中的handleResult()的逻辑)
    assertThat(workInfo.getState(), is(WorkInfo.State.ENQUEUED));
}
复制代码

使用WorkManager 2.1.0进行测试

从2.1.0版本开始,WorkManager提供了新的API,能更方便的测试Worker,ListenableWorker,以及ListenableWorker的变体(CoroutineWorker 和RxWorker)。

以前,为了测试任务,须要使用WorkManagerTestInitHelper来初始化WorkManager。在2.1.0中,不必定要使用它。若是只是为了测试任务中的业务逻辑,不再须要使用WorkManagerTestInitHelper。

测试ListenableWorker和它的变体

为了测试ListenableWorker和它的变体,可使用TestListenableWorkerBuilder。这个建造器能够建立一个ListenableWorker的实例,用来测试任务中的业务逻辑。

package com.example.hero.workmgr;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;
import androidx.concurrent.futures.ResolvableFuture;
import androidx.work.ListenableWorker;
import androidx.work.WorkerParameters;

import com.google.common.util.concurrent.ListenableFuture;

public class SleepWorker extends ListenableWorker {
    private ResolvableFuture<Result> mResult;
    private Handler mHandler;
    private final Object mLock;
    private Runnable mRunnable;

    public SleepWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);
        mResult = ResolvableFuture.create();
        mHandler = new Handler(Looper.getMainLooper());
        mLock = new Object();
    }

    @NonNull
    @Override
    public ListenableFuture<Result> startWork() {
        mRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (mLock) {
                    mResult.set(Result.success());
                }
            }
        };

        mHandler.postDelayed(mRunnable, 1000L);
        return mResult;
    }

    @Override
    public void onStopped() {
        super.onStopped();
        if (mRunnable != null) {
            mHandler.removeCallbacks(mRunnable);
        }
        synchronized (mLock) {
            if (!mResult.isDone()) {
                mResult.set(Result.failure());
            }
        }
    }
}

复制代码

为了测试SleepWorker,先用TestListenableWorkerBuilder建立了一个Worker的实例。这个建立器也能够用来设置标签,输入和尝试运行次数等参数。

@Test
public void testSleepWorker() throws Exception{
    //直接建立了一个worker实例,调用它的方法
    ListenableWorker worker = TestListenableWorkerBuilder.from(ApplicationProvider.getApplicationContext(), SleepWorker.class).build();
    ListenableWorker.Result result = worker.startWork().get();
    assertThat(result, is(ListenableWorker.Result.success()));
}
复制代码

测试任务

有一个任务以下:

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

    @NonNull
    @Override
    public Result doWork() {
        try {
            Thread.sleep(2000);
        }catch (Exception e){
            e.printStackTrace();
        }
        return Result.success();
    }
}
复制代码

使用TestWorkerBuilder进行测试。TestWorkerBuilder容许指定运行任务的线程池。

@Test
public void testThreadSleepWorker() throws Exception {
    Sleep woker = (Sleep) TestWorkerBuilder.from(ApplicationProvider.getApplicationContext(), Sleep.class,
            Executors.newSingleThreadExecutor()).build();
    ListenableWorker.Result result = woker.doWork();
    assertThat(result, is(ListenableWorker.Result.success()));
}
复制代码
本站公众号
   欢迎关注本站公众号,获取更多信息