版权声明
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/13278814.html
前言
用于支持Android在后台的任务运行,提供延迟、周期性,约束性需求的后台任务。任务是交给系统统一调度的,适合一些轻量级的后台功能使用。还能支持在Doze模式下运行后台任务,WorkManager会在Doze模式的窗口期运行任务。
WorkManager的设计用意就是取代后台服务,由系统统一管理你的周期性后台服务,并且自动兼容API23以下,API23以下自动在底层使用AlarmManager + BroadcastReceiver实现,而高于API23会使用JobScheduler实现。所以这是一个能取代闹钟定时器的后台功能。并且在高版本里闹钟功能其实已经不太能正常使用了。使用WorkManager取代所有周期或者长时间的后台工作是必需必要的。
转载请注明来源:https://www.cnblogs.com/guanxinjing/p/13278814.html
WorkManager特性
1.可以在设备睡眠时执行任务,但是只在唤醒窗口期间执行doWork,所以长时间的定位触发并不是准确的(请了解Doze模式)
2.周期任务只能最小15分钟触发一次
3.请注意这个特性! 在任务创建并且入队后,app被后台清理了,任务不会执行. 但是在app重新启动后,只要定时时间已经到达,任务就会在app启动的时候立刻执行. 这个特性引起的问题请阅读这个博客 https://www.cnblogs.com/guanxinjing/p/15951431.html
依赖
// (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可以,按组控制work. 比如多个OneTimeWorkRequest 都添加相同的tag, 再用这个tag进行统一的取消.
OneTimeWorkRequest oneRequest = new OneTimeWorkRequest.Builder(CheckNetworkWorker.class) .setInitialDelay(30, TimeUnit.SECONDS) .addTag(TAG) .setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.MINUTES) .build(); mOneRequestId = oneRequest.getId(); WorkManager.getInstance(context).enqueue(oneRequest);
用tag取消
WorkManager.getInstance(context).cancelAllWorkByTag(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.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) { } });
链式任务
请注意! 在任务链中的某个Work执行返回 Result.failure() ,那么剩下的任务都不会执行了
代码:
private fun initListener() { btn1.setOnClickListener { val data = Data.Builder() .putString("data","初始数据") .build() val aWorkRequest = OneTimeWorkRequest.Builder(AWork::class.java) .setInitialDelay(5, TimeUnit.SECONDS) .addTag("zh") .setInputData(data) .build() val bWorkRequest = OneTimeWorkRequest.Builder(BWork::class.java) .setInitialDelay(5, TimeUnit.SECONDS) .addTag("zh") .build() val cWorkRequest = OneTimeWorkRequest.Builder(CWork::class.java) .setInitialDelay(5, TimeUnit.SECONDS) .addTag("zh") .build() /** * 按顺序执行work, 首先执行用beginWith添加的work然后在执行按then顺序添加的work */ WorkManager.getInstance(this) .beginWith(aWorkRequest) //首先 .then(bWorkRequest) //然后 .then(cWorkRequest) .enqueue() } } public class AWork(@NonNull context: Context, @NonNull workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { /* 接收外部传入的数据 */ Log.e("zh", "AWork doWork 传入数据 = ${inputData.getString("data")}") /* 将数据传给下一个work */ val data = Data.Builder() .putString("type","a") .build() return Result.success(data) } } public class BWork(@NonNull context: Context, @NonNull workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { /* 接收上一个work数据 */ Log.e("zh", "BWork doWork 上个work传入数据 = ${inputData.getString("type")}") val data = Data.Builder() .putString("type","b") .build() return Result.success(data) } } public class CWork(@NonNull context: Context, @NonNull workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { Log.e("zh", "CWork doWork 上个work传入数据 = ${inputData.getString("type")}") return Result.success() } }
结果:
2022-03-03 11:21:59.786 8651-8705/com.example.myapplication E/zh: AWork doWork 传入数据 = 初始数据 2022-03-03 11:22:04.873 8651-8707/com.example.myapplication E/zh: BWork doWork 上个work传入数据 = a 2022-03-03 11:22:10.035 8651-8710/com.example.myapplication E/zh: CWork doWork 上个work传入数据 = b
异步链式任务
一些需要注意的点:
1.这种组合式的异步链式任务,在最后一个任务(下面的代码是FruitWork),会无法从前面2个异步任务里传入值(按理说是应该有传值办法的,个人还在研究中)
2.关于延迟时间, 如果你在相同位置的2个异步任务里都添加了 setInitialDelay(2, TimeUnit.SECONDS). 这个属性, 不管里面添加的数值是多少时间, 都会以最大的时间触发, 比如下面代码中的 AppleWork 设置了2秒延迟, ApricotWork 设置了5秒延迟. 实际上AppleWork 的延迟也会变成5秒(变成2个任务差不多同步执行). 但是如果你其中一个不设置 setInitialDelay() 那么2个work就会有错开时间的效果. 比如下面的BananaWork 与 BlueberryWork. 所以,如果你非要有异步,建议你在doWork直接写线程睡眠
代码
@SuppressLint("EnqueueWork") private fun initListener() { btn1.setOnClickListener { val apple = OneTimeWorkRequest.Builder(AppleWork::class.java) .setInitialDelay(2, TimeUnit.SECONDS) .addTag("fruit") .build() val banana = OneTimeWorkRequest.Builder(BananaWork::class.java) .addTag("fruit") .build() val apricot = OneTimeWorkRequest.Builder(ApricotWork::class.java) .setInitialDelay(5, TimeUnit.SECONDS) .addTag("fruit") .build() val blueberry = OneTimeWorkRequest.Builder(BlueberryWork::class.java) .setInitialDelay(5, TimeUnit.SECONDS) .addTag("fruit") .build() val fruit = OneTimeWorkRequest.Builder(FruitWork::class.java) .setInitialDelay(1, TimeUnit.SECONDS) .addTag("fruit") .build() val workContinuation1: WorkContinuation = WorkManager.getInstance(this) .beginWith(apple) .then(banana) val workContinuation2: WorkContinuation = WorkManager.getInstance(this) .beginWith(apricot) .then(blueberry) WorkContinuation.combine(arrayListOf(workContinuation1, workContinuation2)).then(fruit).enqueue() } } public class AppleWork(@NonNull context: Context, @NonNull workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { Log.e("zh", "AppleWork doWork") return Result.success() } } public class BananaWork(@NonNull context: Context, @NonNull workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { Log.e("zh", "BananaWork doWork") return Result.success() } } public class ApricotWork(@NonNull context: Context, @NonNull workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { // Thread.sleep(2000) Log.e("zh", "ApricotWork doWork") return Result.success() } } public class BlueberryWork(@NonNull context: Context, @NonNull workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { Log.e("zh", "BlueberryWork doWork") // Thread.sleep(2000) return Result.success() } } public class FruitWork(@NonNull context: Context, @NonNull workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { Log.e("zh", "FruitWork doWork") return Result.success() } }
结果:
2022-03-03 15:27:40.507 15181-15225/com.example.myapplication E/zh: ApricotWork doWork 2022-03-03 15:27:40.535 15181-15226/com.example.myapplication E/zh: AppleWork doWork 2022-03-03 15:27:40.678 15181-15229/com.example.myapplication E/zh: BananaWork doWork 2022-03-03 15:27:45.617 15181-15225/com.example.myapplication E/zh: BlueberryWork doWork 2022-03-03 15:27:50.818 15181-15229/com.example.myapplication E/zh: FruitWork doWork
End
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/13278814.html