赞
踩
大量应用程序都有在后台执行任务的需求。根据需求的不同,Android为后台任务提供了多种解决方案,如JobScheduler,Loader,Service等。如果这些API没有被适当地使用,可能会消耗大量的电量。Android在解决应用程序耗电问题上做了各种尝试,从Doze到App Standby,通过各种方式限制和管理应用程序,以保证应用程序不会在后台过量消耗设备电量。WorkManager的出现,则是为应用程序中那些不需要及时完成的任务,提供统一的解决方案,以便在设备电量和用户体验之间达到一个比较好的平衡。
WorkManager最低能兼容API Level 14,并且不需要设备安装有Google Play Services。因此,不用过于担心兼容性问题。
WorkManager依据设备情况选择方案
WorkManager能依据设备的情况,选择不同的执行方案。在API Level 23+,通过JobScheduler来完成任务,而在API Level 23以下的设备中,通过AlarmManager和Broadcast Receivers组合完成任务。但无论采用哪种方案,任务最终都是交由Executor来完成。
1.建立任务容器继承Worker抽象类,实现其抽象方法
public class BackGroundTask extends Worker {
public BackGroundTask(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
return null;
}
}
2.建立任务请求,同时setInputData()方法向Worker传递数据。
分为两种
OneTimeWorkRequest:是只会执行一次的任务请求
PeriodicWorkRequestBuilder:可以周期性的执行任务请求
这里使用OneTimeWorkRequest为例
val task = OneTimeWorkRequest.Builder(NetAutoInfoTask::class.java)
.setInputData(Data.Builder()
.putString("id",id)
.build())
.build()
3.在容器里的dowork可以进行处理后台操作,下面的操作就是简单的在workmananger里使用了同步网络请求作为示例,获取到任务执行请求里设置的data里的数据进行一个参数的填充,在网络请求完成后,通过返回码进行不同的处理
override fun doWork(): Result { //获取data里的id val id = inputData.getString("id") //使用okhttp来请求网络 val okHttpClient = OkHttpClient() val request: Request = Request.Builder() .url(url+id) .addHeader("Cookie",Constants.TOKEN) .addHeader("tenantId",Constants.TENANTID.toString()) .addHeader("currentUser",Constants.CURRENTUSER) .build() val call: Call = okHttpClient.newCall(request) val response = call.execute() val json = response.body?.string() val jsonobject = JSONObject(json) val code = jsonobject.getInt("code") when(code) { 200-> { //布置值入code val content = Gson().fromJson(jsonobject.getString("result"),FramWorkAutoList.ResultDTO.ContentDTO::class.java) MyApplication.getViewModelCenter().frameAutoFrameInfoModel.setDatainfo(content) return Result.success() } else ->{ //再请求失败的时候可以建立一个新的data容器保存失败结果并提交 var data = Data.Builder() data.putString("errorCode",code.toString()) return Result.failure(data.build()) } } return Result.failure() }
在处理请求失败的片段里将错误码放入data容器里,然后回到WorkMananger观察任务的状态
任务在提交给系统后,通过WorkInfo获知任务的状态,WorkInfo包含了任务的id,tag,以及Worker对象传递过来的outputData,以及任务当前的状态。有三种方式可以得到WorkInfo对象。
WorkManager.getWorkInfosByTag()
WorkManager.getWorkInfoById()
WorkManager.getWorkInfosForUniqueWork()
如果你希望能够实时获知任务的状态。这三个方法还有对应的LiveData方法。
WorkManager.getWorkInfosByTagLiveData()
WorkManager.getWorkInfoByIdLiveData()
WorkManager.getWorkInfosForUniqueWorkLiveData()
通过LiveData,我们便可以在任务状态发生变化的时候,收到通知。
fun taskPost(workRequest:WorkRequest):WorkManager { val workManager = WorkManager.getInstance(MyApplication.getContext()) workManager.enqueue(workRequest) return workManager } taskPost(task).getWorkInfoByIdLiveData(task.id).observe(this,{ when(it.state) { WorkInfo.State.SUCCEEDED-> { Log.e("加载成功","成功") } WorkInfo.State.FAILED-> { postToast(this,"获取详情失败") } else ->{} } })
6.取消任务。与观察任务类似的,我们也可以根据Id或者Tag取消某个任务,或者取消所有任务。
WorkManager.cancelAllWork()
WorkManager.cancelAllWorkByTag(tag)
WorkManager.cancelWorkById(id)
有三种方式取消任务执行请求。
第一种是取消所有的任务
第二种是通过给任务请求设置的tag(OneTimeWorkRequest.Builder().setTag().Build())来取消某个或某些任务,多个任务可以使用同一个tag
第三种是通过任务请求的id来取消指定的任务,通过源码发现任务请求在执行完build时会生成一个唯一的id
/**
* Builds a {@link WorkRequest} based on this {@link Builder}.
*
* @return A {@link WorkRequest} based on this {@link Builder}
*/
public final @NonNull W build() {
W returnValue = buildInternal();
// Create a new id and WorkSpec so this WorkRequest.Builder can be used multiple times.
mId = UUID.randomUUID();
mWorkSpec = new WorkSpec(mWorkSpec);
mWorkSpec.id = mId.toString();
return returnValue;
}
7.对任务设置约束条件
可以指定任务请求在某些条件下执行或者不执行
val constraints = Constraints.Builder() //需要在网络链接上的时候执行 constraints.setRequiredNetworkType(NetworkType.CONNECTED) //需要在电量不低的时候执行 constraints.setRequiresBatteryNotLow(true) //需要在设备挂起的时候执行 constraints.setRequiresDeviceIdle(true) //充电的时候执行 constraints.setRequiresCharging(true) ........//其他约束条件 constraints.build() val task = OneTimeWorkRequest.Builder(NetAutoInfoTask::class.java) .setConstraints(constraints.build()) .setInputData(Data.Builder() .putString("id",id) .build()) .build()
8.使用任务链高效的组合业务逻辑
使用workmananger执行小说后台下载任务以及压缩任务,先对指定点击的章节进行下载,然后再对指定的章节文件进行解压,并且在过程中持续返回进度
可以进行对当前所有的livedatalist进行一个数据观察来决定之后的流程
/** * 开始进行指定章节下载任务 */ fun startNewDownloadTask(aid: String, vid: String) { if (!checkFileExits(context?.externalCacheDir?.path + "/" + aid + vid + ".zip")) { //下载 val downloadReqsuet = OneTimeWorkRequest.Builder(DownLoadTask::class.java) .addTag("download") .setInputData(Data.Builder() .putString("aid",aid) .putString("vid",vid) .build()).build() listUUID.add(downloadReqsuet.id) workManager.enqueue(downloadReqsuet) } } /** * 开始进行指定章节解压任务 */ fun startGzip(aid: String,vid: String) { //目录不存在 if (!FileUtils.isDir(context.externalCacheDir?.path + "/" + aid+"/"+vid)) { val gzipRequset = OneTimeWorkRequest.Builder(GZipTask::class.java) .addTag("gzip") .setInputData(Data.Builder().putString("aid",aid) .putString("vid",vid).build()) .build() listUUID.add(gzipRequset.id) workManager.enqueue(gzipRequset) } }
使用addTag函数对任务请求进行标识,然后我们在workmananger里通过tag获取对应的workinfoslivedata进行数据的处理
//对于压缩的处理 workManager.getWorkInfosByTagLiveData("gzip").observe(this,{ it.forEach { when (it.state) { WorkInfo.State.SUCCEEDED -> { //解压成功 //执行视图更新任务 } WorkInfo.State.ENQUEUED -> { } WorkInfo.State.RUNNING -> { } WorkInfo.State.FAILED -> { //解压出现问题 } else -> { } } } }) workManager.getWorkInfosByTagLiveData("download").observe(this, { it.forEach { when (it.state) { WorkInfo.State.SUCCEEDED -> { //下载完成 //开始执行对应章节的压缩任务 //示例代码 if (FileUtils.isFileExists(context?.externalCacheDir?.path+"/"+aid+vid+".zip")) { workCenter.startGzip(aid,vid) } } WorkInfo.State.ENQUEUED -> { } WorkInfo.State.RUNNING -> { //下载中的状态 } WorkInfo.State.FAILED -> { //下载失败 //执行适配器更新 } else -> { } } } })
这一阶段已经处理完毕了,接下来就是点击阅读了,下载的文件为电子书epub格式,epub格式简单的来看其实就是压缩包,里面包含了图片和文本以及目录文件,我们只需要取得目录文件,使用固定的规则读取即可。
class PageInitTask(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { val cid = inputData.getString("cid") val aid = inputData.getString("aid") val vid = inputData.getString("vid") val path = inputData.getString("filePath") //根据opf文件获取图片集合 val chaptor = BookCenter.getChaptor(applicationcontext,filepath) //根据opf文件获取章节文本 val epubDatas = mutableListOf<EpubData>() val path = File(context.externalCacheDir?.path+"/"+chaptor.aid+"/"+chaptor.vid) chaptor.chapters.forEach { //具体每一章节的文件路径 var cpath = path.path+"/"+it.cid //插图筛选出来 val listpic = getPicListByContent(readContent(cpath)!!, Host.RESULT) listpic.forEach{ epubDatas.add(EpubData(path.path+"/"+it.trim(), EpubData.TYPE.IMG)) } epubDatas.add(EpubData(readContent(cpath), EpubData.TYPE.TEXT)) } //todo val data = Data.Builder() .putString("aid",aid) .putString("vid",vid) .putString("cid",cid) //转为json传输,实际上持久化在数据库更好 .putString("data",mGson.toJson(epubDatas)) .build() //返回处理结果 return Result.success(data) } } -------------------------------------处理小说事件 workManager.getWorkInfosByTagLiveData("download").observe(this, { it.forEach { when (it.state) { WorkInfo.State.SUCCEEDED -> { //处理完成 //加载入ui mpageview.initepub(mGson.fromJson(inputdata.getString("data")......)) } else -> { } } } })
9 . Worker 的各种状态说明
在Worker 生命周期内,会经历不同的 State
如果有尚未完成的前提性工作,则工作处于 BLOCKED State。
如果工作能够在满足 约束条件 和时机条件后立即运行,则被视为处于 ENQUEUED 状态。
当 Worker 在活跃地执行时,其处于 RUNNING State。
如果 Worker 返回 Result.success(),则被视为处于 SUCCEEDED 状态。这是一种终止 State;只有 OneTimeWorkRequest 可以进入这种 State。
如果 Worker 返回 Result.failure(),则被视为处于 FAILED 状态。这也是一个终止 State;只有 OneTimeWorkRequest 可以进入这种 State。所有依赖工作也会被标记为 FAILED,并且不会运行。
当取消尚未终止的 WorkRequest 时,它会进入 CANCELLED State。所有依赖工作也会被标记为 CANCELLED,并且不会运行。
开发者经常需要处理后台任务,如果处理后台任务所采用的API没有被正确使用,那么很可能会消耗大量设备的电量。Android出于设备电量的考虑,为开发者提供了WorkManager,旨在将一些不需要及时完成的任务交给它来完成。虽然WorkManager宣称,能够保证任务得到执行。在真实的设备中,执行情况可能不如人意,比如有的rom是不允许AlarmManager被唤起,那么执行不能得到保证
在模拟器中进行测试,模拟器采用的是Google原生系统,发现无论是彻底退出应用程序,或是重启设备,任务都能够被执行。所以,WorkManager在真实设备中不能正常使用,根本就是系统的问题。使用WorkManager作为解决方案时要慎重。
周期任务的实际执行,与所设定的时间差别较大。执行时间看起来并没有太明显的规律。并且在任务执行完成后,WorkInfo并不会收到Success的通知。因为Success和Failure都属于终止类的通知。如果发出这类通知,则表明任务彻底结束,而周期任务不会彻底终止,会一直执行下去,在使用LiveData观察周期任务时,不会收到Success这类的通知。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。