return Worker.Result.SUCCESS
}
doWork要求最后返回一个Result,这个Result是一个枚举,它有几个固定的值:
- FAILURE 任务失败。
- RETRY 遇到暂时性失败,此时可使用WorkRequest.Builder.setBackoffCriteria(BackoffPolicy, long, TimeUnit)来重试。
- SUCCESS 任务成功。
看到这里我就很奇怪,官方不推荐我们使用枚举,但是自己却一直在用,什么意思?
2.2WorkRequest
也是一个抽象类,可以对Work进行包装,同时装裱上一系列的约束(Constraints)
,这些Constraints用来向系统指明什么条件下,或者什么时候开始执行任务。
WorkManager向我们提供了WorkRequest的两个子类:
- OneTimeWorkRequest 单次任务。
- PeriodicWorkRequest 周期任务。
val request1 = PeriodicWorkRequestBuilder(60,TimeUnit.SECONDS).build()
val request2 = OneTimeWorkRequestBuilder().build()
从代码中可以看到,我们应该使用不同的构造器来创建对应的WorkRequest。
接下来我们看看都有哪些约束:
- public boolean requiresBatteryNotLow ():执行任务时电池电量不能偏低。
- public boolean requiresCharging ():在设备充电时才能执行任务。
- public boolean requiresDeviceIdle ():设备空闲时才能执行。
- public boolean requiresStorageNotLow ():设备储存空间足够时才能执行。
addContentUriTrigger
@RequiresApi(24)
public @NonNull Builder addContentUriTrigger(Uri uri, boolean triggerForDescendants)
指定是否在(Uri指定的)内容更新时执行本次任务(只能用于Api24及以上版本)。瞄了一眼源码发现了一个ContentUriTriggers,这什么东东?
public final class ContentUriTriggers implements Iterable<ContentUriTriggers.Trigger> {
private final Set mTriggers = new HashSet<>();
…
public static final class Trigger {
private final @NonNull Uri mUri;
private final boolean mTriggerForDescendants;
Trigger(@NonNull Uri uri, boolean triggerForDescendants) {
mUri = uri;
mTriggerForDescendants = triggerForDescendants;
}
特么惊呆了,居然是个HashSet,而HashSet的核心是个HashMap啊,谷歌声明不建议用HashMap,当然也就不建议用HashSet,可是官方自己在背地里面干的这些勾当啊…
setRequiredNetworkType
public void setRequiredNetworkType (NetworkType requiredNetworkType)
指定任务执行时的网络状态。其中状态见下表:
|枚举|状态| |-|-| |NOT_REQUIRED|不需要网络| |CONNECTED|任何可用网络| |UNMETERED|需要不计量网络,如WiFi| |NOT_ROAMING|需要非漫游网络| |METERED|需要计量网络,如4G|
setRequiresBatteryNotLow
public void setRequiresBatteryNotLow (boolean requiresBatteryNotLow)
指定设备电池电量低于阀值时是否启动任务,默认false。
setRequiresCharging
public void setRequiresCharging (boolean requiresCharging)
指定设备在充电时是否启动任务。
setRequiresDeviceIdle
public void setRequiresDeviceIdle (boolean requiresDeviceIdle)
指明设备是否为空闲时是否启动任务。
setRequiresStorageNotLow
public void setRequiresStorageNotLow (boolean requiresStorageNotLow)
指明设备储存空间低于阀值时是否启动任务。
给任务加约束:
val myConstraints = Constraints.Builder()
.setRequiresDeviceIdle(true)//指定{@link WorkRequest}运行时设备是否为空闲
.setRequiresCharging(true)//指定要运行的{@link WorkRequest}是否应该插入设备
.setRequiredNetworkType(NetworkType.NOT_ROAMING)
.setRequiresBatteryNotLow(true)//指定设备电池是否不应低于临界阈值
.setRequiresCharging(true)//网络状态
.setRequiresDeviceIdle(true)//指定{@link WorkRequest}运行时设备是否为空闲
.setRequiresStorageNotLow(true)//指定设备可用存储是否不应低于临界阈值
.addContentUriTrigger(myUri,false)//指定内容{@link android.net.Uri}时是否应该运行{@link WorkRequest}更新
.build()
val request = PeriodicWorkRequestBuilder(24,TimeUnit.SECONDS)
.setConstraints(myConstraints)//注意看这里!!!
.build()
给任务加标签分组
val request1 = OneTimeWorkRequestBuilder()
.addTag(“A”)//标签
.build()
val request2 = OneTimeWorkRequestBuilder()
.addTag(“A”)//标签
.build()
上述代码我给两个相同任务的request都加上了标签,使他们成为了一个组:A组。这样的好处是以后可以直接控制整个组就行了,组内的每个成员都会受到影响。
2.3 WorkManager
经过上面的操作,相信我们已经能够成功创建request了,接下来我们就需要把任务放进任务队列,我们使用WorkManager
。
WorkManager
是个单例,它负责调度任务并且监听任务状态。
WorkManager.getInstance().enqueue(request)
当我们的request
入列后,WorkManager会给它分配一个work ID
,之后我们可以使用这个work id
来取消或者停止任务:
WorkManager.getInstance().cancelWorkById(request.id)
注意:WorkManager并不一定能结束任务,因为任务有可能已经执行完毕了。
同时,WorkManager还提供了其他结束任务的方法:
- cancelAllWork():取消所有任务。
- cancelAllWorkByTag(tag:String):取消一组带有相同标签的任务。
- cancelUniqueWork(uniqueWorkName:String):取消唯一任务。
2.4WorkStatus
当WorkManager把任务加入队列后,会为每个WorkRequest对象提供一个LiveData(如果这个东东不了解的话赶紧去学)。 LiveData持有WorkStatus;通过观察该 LiveData, 我们可以确定任务的当前状态, 并在任务完成后获取所有返回的值。
val liveData: LiveData = WorkManager.getInstance().getStatusById(request.id)
我们来看这个WorkStatus
到底都包涵什么,我们点进去看它的源码:
public final class WorkStatus {
private @NonNull UUID mId;
private @NonNull State mState;
private @NonNull Data mOutputData;
private @NonNull Set mTags;
public WorkStatus(
@NonNull UUID id,
@NonNull State state,
@NonNull Data outputData,
@NonNull List tags) {
mId = id;
mState = state;
mOutputData = outputData;
mTags = new HashSet<>(tags);
}
我们需要关注的只有State
和Data
这两个属性,首先看State:
public enum State {
ENQUEUED,//已加入队列
RUNNING,//运行中
SUCCEEDED,//已成功
FAILED,//已失败
BLOCKED,//已刮起
CANCELLED;//已取消
public boolean isFinished() {
return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
}
}
这特么又一个枚举。看过代码之后,State枚举其实就是用来给我们做最后的结果判断的。但是要注意其中有个已挂起BLOCKED
,这是啥子情况?通过看它的注释,我们得知,如果WorkRequest
的约束没有通过,那么这个任务就会处于挂起状态。
接下来,Data当然就是我们在任务中doWork
的返回值了
看到这里,我感觉谷歌大佬的设计思维还是非常之强的,把状态和返回值同时输出,非常方便我们做判断的同时来取值,并且这样的设计就可以达到‘多次返回’的效果,有时间一定要去看一下源码,先立个flag!
3. 任务链
在很多场景中,我们需要把不同的任务弄成一个队列,比如在用户注册的时候,我们要先验证手机短信验证码,验证成功后再注册,注册成功后再调登陆接口实现自动登陆。类似这样相似的逻辑比比皆是,实话说笔者以前都是在service里面用rxjava来实现的。但是现在service在Android8.0版本以上系统不能用了怎么办?当然还是用我们今天学到的WorkManager来实现,接下来我们就一起看一下WorkManager的任务链。
3.1链式启动-并发
val request1 = OneTimeWorkRequestBuilder().build()
val request2 = OneTimeWorkRequestBuilder().build()
val request3 = OneTimeWorkRequestBuilder().build()
WorkManager.getInstance().beginWith(request1,request2,request3)
.enqueue()
这样等同于WorkManager把一个个的WorkRequest enqueue
进队列,但是这样写明显更整齐!同时队列中的任务是并行的。
3.2 then操作符-串发
val request1 = OneTimeWorkRequestBuilder().build()
val request2 = OneTimeWorkRequestBuilder().build()
val request3 = OneTimeWorkRequestBuilder().build()
WorkManager.getInstance().beginWith(request1)
.then(request2)
.then(request3)
.enqueue()
上述代码的意思就是先1,1成功后再2,2成功后再3,这期间如果有任何一个任务失败(返回Worker.WorkerResult.FAILURE),则整个队列就会被中断。
在任务链的串行中,也就是两个任务使用了then
操作符连接,那么上一个任务的返回值就会自动转为下一个任务的参数!
3.3 combine操作符-组合
现在我们有个复杂的需求:共有A、B、C、D、E这五个任务,要求AB串行,CD串行,但两个串之间要并发,并且最后要把两个串的结果汇总到E。
我们看到这种复杂的业务逻辑,往往都会吓一跳,但是牛X的谷歌提供了combine
操作符专门应对这种奇葩逻辑,不得不说:谷歌是我亲哥!
val chuan1 = WorkManager.getInstance()
.beginWith(A)
.then(B)
val chuan2 = WorkManager.getInstance()
.beginWith©
.then(D)
WorkContinuation
.combine(chuan1, chuan2)
.then(E)
.enqueue()
4. 唯一链
什么是唯一链,就是同一时间内队列里不能存在相同名称的任务。
val request = OneTimeWorkRequestBuilder().build()
WorkManager.getInstance().beginUniqueWork(“tag”,ExistingWorkPolicy.REPLACE,request,request,request)
从上面代码我们可以看到,首先与之前不同的是,这次我们用的是beginUniqueWork
方法,这个方法的最后一个参数是一个可变长度的数组
,那就证明这一定是一根链条。
然后我们看这个方法的第一个参数,要求输入一个名称,这个名称就是用来标识任务的唯一性。那如果两个不同的任务我们给了相同的名称也是可以的,但是这两个任务在队列中只能存活一个。
最后我们再来看第二个参数ExistingWorkPolicy
,点进去果然又双叒是枚举:
public enum ExistingWorkPolicy {
REPLACE,
KEEP,
APPEND
}
- REPLACE:如果队列里面已经存在相同名称的任务,并且该任务处于挂起状态则替换之。
- KEEP:如果队列里面已经存在相同名称的任务,并且该任务处于挂起状态,则什么也不做。
- APPEND:如果队列里面已经存在相同名称的任务,并且该任务处于挂起状态,则会缓存新任务。当队列中所有任务执行完毕后,以这个新任务做为序列的第一个任务。
六、总结
看到这里相信大家对于WorkManager的基本用法已经了解的差不多了吧!笔者对WorkManager的了解也还不够多,欢迎大家多多留言交流!
另外通过这次对WorkManager的学习,我们也看到官方在代码里面也仍旧在用一些他自己不推荐使用的东西,比如HashMap
、HashSet
、Enum
等,只许州官放火不许百姓点灯?这很谷歌!其实不是的,所谓万事无绝对,只要你够自信,自己做好取舍,掌握平衡,用什么还是由你自己做主!
最后
目前已经更新的部分资料:
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
所谓万事无绝对,只要你够自信,自己做好取舍,掌握平衡,用什么还是由你自己做主!
最后
目前已经更新的部分资料:
[外链图片转存中…(img-WlYnqITP-1715602516536)]
[外链图片转存中…(img-H6bjp3q5-1715602516537)]
[外链图片转存中…(img-WaS6mHcl-1715602516539)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!