前言
用于支持Android在后台的任务运行,提供延迟、周期性,约束性需求的后台任务。任务是交给系统统一调度的,适合一些轻量级的后台功能使用。还能支持在Doze模式下运行后台任务,WorkManager会在Doze模式的窗口期运行任务。
WorkManager的设计用意就是取代后台服务,由系统统一管理你的周期性后台服务,并且自动兼容API23以下,API23以下自动在底层使用AlarmManager + BroadcastReceiver实现,而高于API23会使用JobScheduler实现。所以这是一个能取代闹钟定时器的后台功能。并且在高版本里闹钟功能其实已经不太能正常使用了。使用WorkManager取代所有周期或者长时间的后台工作是必需必要的。
依赖
// (Java only) implementation "androidx.work:work-runtime:2.3.4"//java 语言选这个 // Kotlin + coroutines implementation "androidx.work:work-runtime-ktx:2.3.4"//kotlin 选这个 // optional - RxJava2 support implementation "androidx.work:work-rxjava2:2.3.4"//配合rxjava2 使用
一个简单的小Demo快速了解
照例用一个极简的demo来运行体验一下。
创建work,继承Worker,实现doWork方法,这个方法是执行后台功能实现的地方。
public class MyWork extends Worker { public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { Log.e("调试_临时_log", "this_doWork"); return Result.success();//结果返回为成功 } }
创建Work请求并且添加到WorkManager里:
private void startWork(){ OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class)//一次性Work请求 .setInitialDelay(10, TimeUnit.SECONDS)//初始延迟10秒 .build(); WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);//添加到WorkManager队列中 }
这样我们就可以在10秒后看到log了。
了解Worker基本功能
数据的传入与获取
在work里
public class MyWork extends Worker { public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { String data = getInputData().getString("putData"); Log.e("调试_临时_log", "传入数据 putData = " + data); return Result.success(); } }
在Activity里创建work请求,并且传入数据
private void startWork(){ Data data = new Data.Builder().putString("putData","输入数据").build();//创建需要传入的数据,注意不支持序列化数据传入 OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class)//一次性Work请求 .setInitialDelay(2, TimeUnit.SECONDS) .setInputData(data) .build(); WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);//添加到WorkManager队列中 }
结果:
2020-07-10 17:51:10.638 24649-24698/com.yt.demo E/调试_临时_log: 传入数据 putData = 输入数据
work的返回结果与监听状态
处理了后台任务后总会有成功与否的结果。
在doWork方法里返回结果:
一共有3种结果可以返回,如下注释:
@NonNull @Override public Result doWork() { return Result.success(); // Result.success();//成功 // Result.failure();//失败 // Result.retry();//重试 }
另外成功与失败的结果还能携带数据返回。
@NonNull @Override public Result doWork() { Data data = new Data.Builder().putString("data", "返回数据").build(); return Result.success(data); }
Activity里监听work的状态:
请注意!在这里的监听的返回的数据是LiveData。 这意味着只有当前Activity或者Fragment在前台时才能接收到此数据。
private void startWork(){ OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class)//一次性Work请求 .setInitialDelay(5, TimeUnit.SECONDS) .build(); WorkManager.getInstance(this).getWorkInfoByIdLiveData(oneTimeWorkRequest.getId()).observe(this, new Observer<WorkInfo>() { @Override public void onChanged(WorkInfo workInfo) { switch (workInfo.getState()){ case BLOCKED: Log.e("调试_临时_log", "堵塞"); break; case RUNNING: Log.e("调试_临时_log", "正在运行"); break; case ENQUEUED: Log.e("调试_临时_log", "任务入队"); break; case CANCELLED: Log.e("调试_临时_log", "取消"); break; case FAILED: Log.e("调试_临时_log", "失败"); break; case SUCCEEDED: Log.e("调试_临时_log", "成功"); Log.e("调试_临时_log", "this_data = " + workInfo.getOutputData().getString("data")); break; } } }); WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);//添加到WorkManager队列中 }
结果:
2020-07-10 20:11:20.088 31078-31078/com.yt.demo E/调试_临时_log: 任务入队 2020-07-10 20:11:25.172 31078-31078/com.yt.demo E/调试_临时_log: 正在运行 2020-07-10 20:11:25.195 31078-31078/com.yt.demo E/调试_临时_log: 成功 2020-07-10 20:11:25.195 31078-31078/com.yt.demo E/调试_临时_log: this_data = 返回数据
不选择监听,即刻获得某个Work的当前状态值
try { WorkInfo workInfo = WorkManager.getInstance(MainActivity.this).getWorkInfoById(mOneTimeWorkRequest.getId()).get(); Log.e("调试_临时_log", "this_" + workInfo.getState()); } catch (ExecutionException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); }
work的进度发布
有时候有需求需要知道work任务执行的进度。下面的MyWork模拟发送耗时任务进度,使用setProgressAsync方法发布进度。
public class MyWork extends Worker { public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { for (int i = 0; i < 10; i++) { Data data = new Data.Builder().putInt("Progress", i).build(); setProgressAsync(data); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } return Result.success(); } }
监听进度:
private void startWork() { OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class)//一次性Work请求 .setInitialDelay(5, TimeUnit.SECONDS) .build(); WorkManager.getInstance(this).getWorkInfoByIdLiveData(oneTimeWorkRequest.getId()).observe(this, new Observer<WorkInfo>() { @Override public void onChanged(WorkInfo workInfo) { if (workInfo.getState() == WorkInfo.State.RUNNING) { Log.e("调试_临时_log", "当前进度 = " + workInfo.getProgress().getInt("Progress", -1)); } } }); WorkManager.getInstance(this).enqueue(oneTimeWorkRequest);//添加到WorkManager队列中 }
Work的停止
work的停止,只会work在运行时执行onStopped,已经执行完成去取消任务是不会触发onStopped方法的。
work里的代码:
public class MyWork extends Worker { private boolean mIsStop = false; public MyWork(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { for (int i = 0; i < 10; i++) { if (mIsStop){ break; } Data data = new Data.Builder().putInt("Progress", i).build(); setProgressAsync(data); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } return Result.success(); } @Override public void onStopped() { Log.e("调试_临时_log", "this_onStopped"); mIsStop = true; super.onStopped(); } }
Activity代码:
public class MainActivity extends AppCompatActivity { private ActivityMianDemoBinding mBinding; private OneTimeWorkRequest oneTimeWorkRequest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = ActivityMianDemoBinding.inflate(getLayoutInflater()); setContentView(mBinding.getRoot()); mBinding.btn1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setInitialDelay(5, TimeUnit.SECONDS) .build(); WorkManager.getInstance(MainActivity.this).getWorkInfoByIdLiveData(oneTimeWorkRequest.getId()).observe(MainActivity.this, new Observer<WorkInfo>() { @Override public void onChanged(WorkInfo workInfo) { switch (workInfo.getState()) { case RUNNING: Log.e("调试_临时_log", "当前进度 = " + workInfo.getProgress().getInt("Progress", -1)); break; case CANCELLED: Log.e("调试_临时_log", "this_取消任务"); break; } } }); WorkManager.getInstance(MainActivity.this).enqueue(oneTimeWorkRequest); } }); mBinding.btn2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { WorkManager.getInstance(MainActivity.this).cancelWorkById(oneTimeWorkRequest.getId());//取消任务 } }); } }
结果:
2020-07-13 11:38:27.160 14030-14030/com.yt.cccomponentizationdemo E/调试_临时_log: 当前进度 = 0 2020-07-13 11:38:28.210 14030-14030/com.yt.cccomponentizationdemo E/调试_临时_log: 当前进度 = 1 2020-07-13 11:38:29.199 14030-14030/com.yt.cccomponentizationdemo E/调试_临时_log: 当前进度 = 2 2020-07-13 11:38:29.902 14030-14075/com.yt.cccomponentizationdemo E/调试_临时_log: this_onStopped 2020-07-13 11:38:29.973 14030-14030/com.yt.cccomponentizationdemo E/调试_临时_log: this_取消任务
了解创建Work请求
请求有两种
- OneTimeWorkRequest 一次性Work请求
- PeriodicWorkRequest 周期性Work请求
OneTimeWorkRequest
设置初始延迟时间 setInitialDelay
上面已经有很多例子了,就不在重复说明了
OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setInitialDelay(2, TimeUnit.SECONDS)//设置初始延时时间 .build();
设置传入数据 setInputData
上面已经有很多例子了,就不在重复说明了
Data data = new Data.Builder().putString("data","demo").build(); OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setInputData(data)//设置传入数据 .build();
添加Tag addTag
注意,这个添加Tag有点奇怪。如果一直添加相同的Tag,这个Tag可以被多次添加,并且在使用getWorkInfosByTagLiveData 进行监听回调时List<WorkInfo>也会有多个,并且无法好像无法删除这个list数量(取消任务也不行)。但是只会返回一次数据。请谨慎使用,我暂时没明白如何使用它。
OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setInitialDelay(2, TimeUnit.SECONDS)//设置初始延时时间 .addTag("tag1")//添加TAG .build(); WorkManager.getInstance(MainActivity.this).getWorkInfosByTagLiveData("tag1").observe(MainActivity.this, new Observer<List<WorkInfo>>() { @Override public void onChanged(List<WorkInfo> workInfos) { if (workInfos == null && workInfos.isEmpty()) { return; } Log.e("调试_临时_log", "this_ workInfos.size = " + workInfos.size()); WorkInfo workInfo = workInfos.get(0); switch (workInfo.getState()) { case RUNNING: Log.e("调试_临时_log", "this_进行中"); break; case CANCELLED: Log.e("调试_临时_log", "this_取消"); break; case SUCCEEDED: Log.e("调试_临时_log", "this_成功"); break; } } }); WorkManager.getInstance(MainActivity.this).enqueue(oneTimeWorkRequest);
设置任务的结果保存时间 keepResultsForAtLeast
oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .keepResultsForAtLeast(10, TimeUnit.MINUTES)//结果延迟保存 .build();
设置退避策略 setBackoffCriteria
一般当我们任务执行失败的时候任务需要重试的时候会用到这个函数,在任务执行失败的时候Worker类的doWork()函数返回Result.RETRY告诉这个任务要重试。那重试的策略就是通过setBackoffCriteria()函数来设置的。
BackoffPolicy有两个值:
BackoffPolicy.LINEAR(每次重试的时间线性增加,比如第一次10分钟,第二次就是20分钟)
BackoffPolicy.EXPONENTIAL(每次重试时间指数增加)。
oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setBackoffCriteria(BackoffPolicy.LINEAR, 10,TimeUnit.MINUTES)//退避策略 线性增加 10分钟重试 .build();
PeriodicWorkRequest
因为前面没有说明过PeriodicWorkRequest,所以这里说明下PeriodicWorkRequest创建的一些细节。首先Builder(MyWork.class, 15 ,TimeUnit.MINUTES) 三个参数,第二个参数是重复触发的时间,第三个参数是单位。
请注意,这里的重复周期时间是有要求的,大于等于15分钟,这个在Builder方法的注释里有说明PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS最小间隔时间。
请再次注意,PeriodicWorkRequest在给WorkManager入队后就会立马执行,所以你如果需要一开始就延迟需要自行设延迟时间。
另外PeriodicWorkRequest的可以设置的属性与OneTimeWorkRequest一致。可以参考上面的说明,这里就不在重复。
PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest.Builder(MyWork.class, 15 ,TimeUnit.MINUTES) .build(); WorkManager.getInstance(MainActivity.this).enqueue(periodicWorkRequest);
约束条件Constraints
Constraints constraints = new Constraints.Builder() .setRequiresDeviceIdle(true)//触发时设备是否为空闲 .setRequiresCharging(true)//触发时设备是否充电 .setRequiredNetworkType(NetworkType.UNMETERED)//触发时网络状态 .setRequiresBatteryNotLow(true)//指定设备电池是否不应低于临界阈值 .setRequiresStorageNotLow(true)//指定设备可用存储是否不应低于临界阈值 .addContentUriTrigger(myUri,false)//指定内容{@link android.net.Uri}时是否应该运行{@link WorkRequest}更新 .build(); OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(MyWork.class) .setConstraints(constraints) .build(); WorkManager.getInstance(MainActivity.this).enqueue(oneTimeWorkRequest);
网络状态条件
public enum NetworkType { /** * 这项工作不需要网络 */ NOT_REQUIRED, /** * 这项工作需要任何有效的网络连接 */ CONNECTED, /** * 这项工作需要未计量的网络连接 */ UNMETERED, /** * 此项工作需要非漫游网络连接 */ NOT_ROAMING, /** * 此项工作需要计量网络连接 */ METERED }
WorkManager
主要用于work的入队与取消,设置监听功能。
任务入队
WorkManager.getInstance(MainActivity.this).enqueue(oneTimeWorkRequest);
取消指定ID任务
WorkManager.getInstance(MainActivity.this).cancelWorkById(oneTimeWorkRequest.getId());
取消全部任务
WorkManager.getInstance(MainActivity.this).cancelAllWork();
创建唯一任务
WorkManager.getInstance(MainActivity.this).beginUniqueWork("unique", ExistingWorkPolicy.REPLACE, oneTimeWorkRequest).enqueue();
或者
WorkManager.getInstance(MainActivity.this).enqueueUniqueWork("unique", ExistingWorkPolicy.REPLACE, oneTimeWorkRequest);
任务类型说明
public enum ExistingWorkPolicy { /** 如果存在具有相同唯一名称的待处理(未完成)任务,请取消并删除它。然后,插入新指定的任务 */ REPLACE, /** * 如果存在具有相同唯一名称的待处理(未完成)任务,则不执行任何操作。 否则,插入新指定的任务。 */ KEEP, /** * 如果存在具有相同唯一名称的待处理(未完成)任务,请将*新指定的任务作为该任务序列所有叶子的子项附加。否则,请插入*新指定的任务作为新序列的开始。 */ APPEND }
监听唯一任务
WorkManager.getInstance(MainActivity.this).getWorkInfosForUniqueWorkLiveData("unique").observe(MainActivity.this, new Observer<List<WorkInfo>>() { @Override public void onChanged(List<WorkInfo> workInfos) { } });
End