WorkManager是为了那些可延后执行的任务而设计,这些任务不须要当即执行,可是须要保证任务能被执行,即便应用退出或者设备重启。例如:java
WorkManager不是为某些进程内的后台任务设计的,这些任务会在app进程退出时被中止,也不是那些须要当即执行的任务。android
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
Worker定义了具体的任务,WorkRequest定义了如何执行以及什么时候执行任务。若是是一次性的任务,能够用O呢TimeWorkRequest,若是是周期性的任务,可使用PeriodicWorkRequest。数组
OneTimeWorkRequest uploadReq = new OneTimeWorkRequest.Builder(UploadWorker.class).build();
复制代码
PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(UploadWorker.class, 10, TimeUnit.SECONDS).build();
复制代码
调用WorkManager的enqueue方法bash
WorkManager.getInstance(ctx).enqueue(uploadReq);
复制代码
任务的具体执行时机依赖于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();
复制代码
在任务的生命周期中,会通过各类状态:
当把任务放入队列中,WorkManager容许检查它们的状态。这些信息能够经过WorkInfo对象获取,包含了任务的id,tag,当前的State和输出的数据。
有如下几个方法获取WorkInfo:
上述方法返回的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:
OneTimeWorkRequest compress =
new OneTimeWorkRequest.Builder(CompressWorker.class)
.setInputMerger(ArrayCreatingInputMerger.class)
.setConstraints(constraints)
.build();
复制代码
当建立一个OneTimeWorkRequest任务链时,有几件事要记住:
若是再也不须要入队的任务执行,能够取消它。取消一个单独的WorkRequest最简单的方法是使用id并调用WorkManager.cancenWorkById(UUID)。
WorkManager.cancelWorkById(workRequest.getId());
复制代码
在底层,WorkManager会检查任务的状态。若是这个任务已经完成,没有任何事情发生。不然,这个任务的状态会转移到CANCELED 而且这个任务之后不会再运行。任何依赖这个任务的其余WorkRequest都会被标记为CANCELED。
另外,若是当前任务正在运行,这个任务会触发ListenableWorker.onStopped()的回调。重写这个方法来处理任何可能的清理工做。
也能够用标签来取消任务,经过调用WorkManager.cancelAllWorkByTag(String)。注意,这个方法会取消全部有这个标签的任务。另外,也能够调用WorkManager.cancelUniqueWork(String)取消带有该独特名字的所有任务。
有几种状况,运行中的任务会被WorkManager终止:
在这些状况下,任务会触发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采起的措施:
若是有一个任务不须要屡次放入队列时,惟一任务会颇有用。例如,若是你的应用须要同步数据到网络,能够入队一个命名为“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));
}
复制代码
从2.1.0版本开始,WorkManager提供了新的API,能更方便的测试Worker,ListenableWorker,以及ListenableWorker的变体(CoroutineWorker 和RxWorker)。
以前,为了测试任务,须要使用WorkManagerTestInitHelper来初始化WorkManager。在2.1.0中,不必定要使用它。若是只是为了测试任务中的业务逻辑,不再须要使用WorkManagerTestInitHelper。
为了测试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()));
}
复制代码