1.1 WorkManager 简介
在 Android 利用开发中,或多或少的会有后台任务的需要,依据需要场景的不同,Android 为后台任务提供了多种不同的解决方案,如 Service、Loader、JobScheduler 和 AlarmManger 等。后台任务通常用在不须要用户感知的性能,并且后台任务执行实现后须要即时敞开工作回收资源,如果没有正当的应用这些 API 就会造成电量的大量耗费。为了解决 Android 电量大量耗费的问题,Android 官网做了各种优化尝试,从 Doze 到 app Standby,通过增加各种限度和管理应用程序过程来包装应用程序不会大量的耗费电量。
为了解决 Android 耗电的问题,Android 提供了 WorkManager,用来对利用中那些不须要及时实现的工作提供一个对立的解决方案,借助 WorkManager,开发者能够轻松调度那些即便在退出利用或重启设施时仍应运行的可延期异步工作。WorkManager 是一套 AP,用来替换先前的 Android 后盾调度 API(包含 FirebaseJobDispatcher、GcmNetworkManager 和 JobScheduler)等组件。WorkManager 须要 API 级别为 14,同时可保障电池续航工夫。
WorkManager 的兼容性体现在可能依据零碎版本,抉择不同的计划来实现,在 API 低于 23 时,采纳 AlarmManager+Broadcast Receiver,高于 23 时采纳 JobScheduler。但无论采纳哪种形式,工作最终都是交由 Executor 来执行。下图展现了 WorkManager 底层作业调度服务的运作流程。
须要留神的是,WorkManager 不是一种新的工作线程,它的呈现不是为了替换其余类型的工作线程。工作线程通常可能立刻执行,并在工作实现后将后果反馈给用户,而 WorkManager 不是即时的,它不能保障工作可能被立刻执行。
1.2 WorkManager 特点
WorkManager 有以下三个特点:
- 用来实现不须要即时实现的工作,如后盾下载开屏广告、上传日志信息等;
- 可能保障工作肯定会被执行;
- 兼容性强。
针对不须要即时实现的工作
在 Android 开发中,常常会遇到后盾下载、上传日志信息等需要,一般来说,这些工作是不须要立刻实现的,如果咱们本人应用来治理这些工作,逻辑可能会十分负责,并且如果解决不失当会造成大量的电量耗费。
后盾延时工作
WorkManager 可能保障工作肯定会被执行,但不是不能保障被立刻执行,也即说在适当的时候被执行。因为 WorkManager 有本人的数据库,与工作相干的信息和数据就保留到数据库中。所以,只有工作曾经提交到 WorkManager,即便利用推出或者设施重启也不须要放心工作被失落。
兼容性广
WorkManager 可能兼容 API 14,并且不须要你的设施装置 Google Play Services,因而不必放心呈现兼容性问题。
除此之外,WorkManager 还具备许多其余要害劣势。
工作束缚
应用工作束缚明确定义工作运行的最佳条件。例如,仅在设施采纳 Wi-Fi 网络连接时、当设施处于闲暇状态或者有足够的存储空间时再运行。
弱小的调度
WorkManager 容许开发者应用灵便的调度窗口调度工作,以运行一次性或反复工作。还能够对工作进行标记或命名,以便调度惟一的、可替换的工作以及监控或勾销工作组。已调度的工作存储在外部托管的 SQLite 数据库中,由 WorkManager 负责确保该工作继续进行,并在设施重新启动后从新调度。此外,WorkManager 遵循低电耗模式等省电性能和最佳做法,因而开发者无需思考电量耗费的问题。
灵便的重试政策
有时工作执行会呈现失败,WorkManager 提供了灵便的重试政策,包含可配置的指数退却政策。
工作链接
对于简单的相干工作,咱们能够应用晦涩天然的界面将各个工作工作链接在一起,这样便能够管制哪些局部依序运行,哪些局部并行运行,如下所示。
WorkManager.getInstance(...)
.beginWith(Arrays.asList(workA, workB))
.then(workC)
.enqueue();
内置线程互操作性
WorkManager 无缝集成 RxJava 和 协程,灵便地插入您本人的异步 API。
1.3 WorkManager 的几个概念
应用 WorkManager 时有几个重要的概念须要留神。
- Worker:工作的执行者,是一个抽象类,须要继承它实现要执行的工作。
- WorkRequest:指定让哪个 Woker 执行工作,指定执行的环境,执行的程序等。要应用它的子类 OneTimeWorkRequest 或 PeriodicWorkRequest。
- WorkManager:治理工作申请和工作队列,发动的 WorkRequest 会进入它的工作队列。
- WorkStatus:蕴含有工作的状态和工作的信息,以 LiveData 的模式提供给观察者。
二、根本应用
2.1 增加依赖
如需开始应用 WorkManager,请先将库导入您的 Android 我的项目中。
dependencies {
def work_version = "2.4.0"
implementation "androidx.work:work-runtime:$work_version"
}
增加依赖项并同步 Gradle 我的项目后。
2.2 定义 Worker
创立一个继承自 Worker 的 Worker 类,而后在 Worker 类的 doWork() 办法中执行要运行的工作,并且须要返回工作状态的后果。例如,在 doWork() 办法实现上传图像的 工作。
public class UploadWorker extends Worker {
public UploadWorker(
@NonNull Context context,
@NonNull WorkerParameters params) {super(context, params);
}
@Override
public Result doWork() {
// Do the work here--in this case, upload the images.
uploadImages();
return Result.success();}
}
在 doWork() 办法中执行的工作最终须要返回一个 Result 类型对象,示意工作执行后果,有三个枚举值。
- Result.success():工作胜利实现。
- Result.failure():工作失败。
- Result.retry():工作失败,依据其重试政策在其余工夫尝试。
2.3 创立 WorkRequest
实现 Worker 的定义后,必须应用 WorkManager 服务进行调度该工作能力运行。对于如何调度工作,WorkManager 提供了很大的灵活性。开发者能够将其安顿为在某段时间内定期运行,也能够将其安顿为仅运行一次。
不管您抉择以何种形式调度工作,请应用 WorkRequest 执行工作的申请。Worker 定义工作单元,WorkRequest(及其子类)则定义工作运行形式和工夫,如下所示。
WorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(UploadWorker.class)
.build();
而后,应用 WorkManager 的 enqueue() 办法将 WorkRequest 提交到 WorkManager,如下所示。
WorkManager
.getInstance(myContext)
.enqueue(uploadWorkRequest);
执行工作器的确切工夫取决于 WorkRequest 中应用的束缚和系统优化形式。
三、办法指南
3.1 WorkRequest
3.1.1 WorkRequest 概览
WorkRequest 次要用于向 Worker 提交工作申请,咱们能够应用 WorkRequest 来解决以下一些常见的场景。
- 调度一次性工作和重复性工作
- 设置工作约束条件,例如要求连贯到 Wi-Fi 网络或正在充电才会执行 WorkRequest
- 确保至多提早肯定工夫再执行工作
- 设置重试和退却策略
- 将输出数据传递给工作
- 应用标记将相干工作分组在一起
WorkRequest 是一个抽象类,它有两个子类,别离是 OneTimeWorkRequest 和 PeriodicWorkRequest,前者实现只执行一次的工作,后者用来实现周期性工作。
3.1.2 一次性工作
如果工作只须要执行一次,那么能够应用 WorkRequest 的子类 OneTimeWorkRequest。对于无需额定配置的简略工作,能够应用 OneTimeWorkRequest 类的静态方法 from(),如下所示。
WorkRequest myWorkRequest = OneTimeWorkRequest.from(MyWork.class);
对于更简单的工作,则能够应用构建器的形式来创立 WorkRequest,如下所示。
WorkRequest uploadWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.build();
3.1.3 定期工作
如果须要定期运行某些工作,那么能够应用 PeriodicWorkRequest。例如,可能须要定期备份数据、定期下载利用中的陈腐内容或者定期上传日志到服务器等。
PeriodicWorkRequest saveRequest =
new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class, 1, TimeUnit.HOURS)
.build();
下面的代码定义了一个运行工夫距离定为一小时的定期工作。不过,工作器的确切执行工夫取决于您在 WorkRequest 对象中设置的束缚以及零碎执行的优化。
如果工作的性质对运行的工夫比拟敏感,能够将 PeriodicWorkRequest 配置为在每个工夫距离的灵便时间段内运行,如图 1 所示。
如需定义具备灵便时间段的定期工作,请在创立 PeriodicWorkRequest 时传递 flexInterval 和 repeatInterval 两个参数,如下所示。
WorkRequest saveRequest =
new PeriodicWorkRequest.Builder(SaveImageToFileWorker.class,
1, TimeUnit.HOURS,
15, TimeUnit.MINUTES)
.build();
下面的代码的含意是在每小时的最初 15 分钟内运行定期工作。
3.1.4 工作束缚
为了让工作在指定的环境下运行,咱们能够给 WorkRequest 增加约束条件,常见的约束条件如下所示。
- NetworkType:束缚运行工作所需的网络类型,例如 Wi-Fi (UNMETERED)。
- BatteryNotLow:如果设置为 true,那么当设施处于“电量有余模式”时,工作不会运行。
- RequiresCharging:如果设置为 true,那么工作只能在设施充电时运行。
- DeviceIdle:如果设置为 true,则要求用户的设施必须处于闲暇状态能力运行工作。
- StorageNotLow:如果设置为 true,那么当用户设施上的存储空间有余时,工作不会运行。
例如,以下代码会构建了一个工作申请,该工作申请仅在用户设施正在充电且连贯到 Wi-Fi 网络时才会运行。
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build();
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setConstraints(constraints)
.build();
如果在工作运行时不满足某个束缚,那么 WorkManager 将进行工作,并且零碎将在满足所有束缚后重试工作。
3.1.5 提早工作
如果工作没有束缚,并且所有束缚都失去了满足,那么当工作退出队列时零碎可能会抉择立刻运行该工作。如果您不心愿工作立刻运行,能够将工作指定为在通过一段最短初始延迟时间后再启动。
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setInitialDelay(10, TimeUnit.MINUTES)
.build();
下面代码的作用是,设置工作在退出队列后至多通过 10 分钟后再运行。
3.1.6 重试和退却政策
如果须要让 WorkManager 重试工作,能够应用工作器返回 Result.retry(),而后零碎将依据退却延迟时间和退却政策从新调度工作。
- 退却延迟时间指定了首次尝试后重试工作前的最短等待时间,个别不能超过 10 秒(或者 MIN_BACKOFF_MILLIS)。
- 退却政策定义了在后续重试过程中,退却延迟时间随工夫以怎么的形式增长。WorkManager 反对 2 个退却政策,即 LINEAR 和 EXPONENTIAL。
每个工作申请都有退却政策和退却延迟时间。默认政策是 EXPONENTIAL,延迟时间为 10 秒,开发者能够在工作申请配置中替换此默认设置。
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
.build();
3.1.7 标记 WorkRequest
每个工作申请都有一个惟一标识符,该标识符可用于标识该工作,以便勾销工作或察看其进度。如果有一组在逻辑上相干的工作,对这些工作项进行标记可能也会很有帮忙。为 WorkRequest 增加标记应用的是 addTag() 办法,如下所示。
WorkRequest myWorkRequest =
new OneTimeWorkRequest.Builder(MyWork.class)
.addTag("cleanup")
.build();
最初,能够向单个工作申请增加多个标记,这些标记在外部以一组字符串的模式进行存储。对于工作申请,咱们能够通过 WorkRequest.getTags() 检索其标记集。
3.1.8 调配输出数据
有时候,工作须要输出数据能力失常运行。例如解决图片上传工作时须要上传图片的 URI 作为输出数据,咱们将此种场景称为调配输出数据。
输出值以键值对的模式存储在 Data 对象中,并且能够在工作申请中设置,WorkManager 会在执行工作时将输出 Data 传递给工作,Worker 类可通过调用 Worker.getInputData() 拜访输出参数,如下所示。
public class UploadWork extends Worker {public UploadWork(Context appContext, WorkerParameters workerParams) {super(appContext, workerParams);
}
@NonNull
@Override
public Result doWork() {String imageUriInput = getInputData().getString("IMAGE_URI");
if(imageUriInput == null) {return Result.failure();
}
uploadFile(imageUriInput);
return Result.success();}
...
}
// Create a WorkRequest for your Worker and sending it input
WorkRequest myUploadWork =
new OneTimeWorkRequest.Builder(UploadWork.class)
.setInputData(new Data.Builder()
.putString("IMAGE_URI", "http://...")
.build())
.build();
下面的代码展现了如何创立须要输出数据的 Worker 实例,以及如何在工作申请中发送该实例。
3.2 Work 状态
Work 在其整个生命周期内经验了一系列 State 更改,状态的更改分为一次性工作的状态和周期性工作的状态。
3.2.1 一次性工作状态
对于一次性工作申请,工作的初始状态为 ENQUEUED
。在 ENQUEUED
状态下,工作会在满足其 Constraints 和初始提早计时要求后立刻运行。接下来,该工作会转为 RUNNING
状态,而后可能会依据工作的后果转为 SUCCEEDED
、FAILED
状态;或者,如果后果是 retry,它可能会回到 ENQUEUED
状态。在此过程中,随时都能够勾销工作,勾销后工作将进入 CANCELLED
状态。
上图展现了一次性工作的生命周期状态的变动过程,SUCCEEDED、FAILED 和 CANCELLED 均示意此工作的终止状态。如果您的工作处于上述任何状态,WorkInfo.State.isFinished()
都将返回 true。
3.2.2 定期工作状态
胜利和失败状态仅实用于一次性工作和链式工作,定期工作只有一个终止状态 CANCELLED
,这是因为定期工作永远不会完结。每次运行后,无论后果如何,零碎都会从新对其进行调度。
上图展现了定时工作的生命周期状态的变动过程。
3.3 工作治理
3.3.1 惟一工作
在定义了 Worker 和 WorkRequest 之后,最初一步是将工作退出队列,将工作退出队列的最简略办法是调用 WorkManager enqueue()
办法,而后传递要运行的 WorkRequest。在将工作退出队列时须要留神防止反复退出的问题,为了实现此指标,咱们能够将工作调度为惟一工作。
惟一工作可确保同一时刻只有一个具备特定名称的工作实例。与系统生成的 ID 不同,惟一名称是由开发者指定,而不是由 WorkManager 主动生成。惟一工作既可用于一次性工作,也可用于定期工作。您能够通过调用以下办法之一创立惟一工作序列,具体取决于您是调度反复工作还是一次性工作。
- WorkManager.enqueueUniqueWork():用于一次性工作
- WorkManager.enqueueUniquePeriodicWork():用于定期工作
并且,这两个办法都承受 3 个参数。
- niqueWorkName:用于惟一标识工作申请的 String。
- existingWorkPolicy:此 enum 可告知 WorkManager 如果已有应用该名称且尚未实现的惟一工作链,应执行什么操作。如需理解详情,请参阅抵触解决政策。
- work:要调度的 WorkRequest。
上面是应用惟一工作解决反复调度问题,代码如下。
PeriodicWorkRequest sendLogsWorkRequest = new
PeriodicWorkRequest.Builder(SendLogsWorker.class, 24, TimeUnit.HOURS)
.setConstraints(new Constraints.Builder()
.setRequiresCharging(true)
.build())
.build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"sendLogs",
ExistingPeriodicWorkPolicy.KEEP,
sendLogsWorkRequest);
上述代码在 sendLogs 作业时,如果已处于队列中的状况下运行则零碎会保留现有的作业,并且不会增加新的作业。
3.3.2 抵触解决策略
有时候,工作的调度会呈现抵触,此时咱们须要告知 WorkManager 在发生冲突时要执行的操作,能够通过在将工作退出队列时传递一个枚举来实现此目标。对于一次性工作,零碎提供了一个 ExistingWorkPolicy 枚举累,它反对用于解决抵触的选项有如下几个。
- REPLACE:用新工作替换现有工作。此选项将勾销现有工作。
- KEEP:保留现有工作,并疏忽新工作。
- APPEND:将新工作附加到现有工作的开端。此政策将导致您的新工作链接到现有工作,在现有工作实现后运行。
现有工作将成为新工作的先决条件,如果现有工作变为 CANCELLED
或 FAILED
状态,新工作也会变为 CANCELLED
或 FAILED
。如果您心愿无论现有工作的状态如何都运行新工作,那么能够应用 APPEND_OR_REPLACE
。APPEND_OR_REPLACE
的作用是不论状态变为 CANCELLED
或 FAILED
状态,新工作仍会运行。
3.4 察看工作状态
在将工作退出到队列后,咱们能够依据 name、id 或与其关联的 tag 在 WorkManager 中查问工作的相干信息,并且查看它的状态,波及的办法有如下几个。
// by id
workManager.getWorkInfoById(syncWorker.id); // ListenableFuture<WorkInfo>
// by name
workManager.getWorkInfosForUniqueWork("sync"); // ListenableFuture<List<WorkInfo>>
// by tag
workManager.getWorkInfosByTag("syncTag"); // ListenableFuture<List<WorkInfo>>
该查问会返回 WorkInfo 对象的 ListenableFuture,次要蕴含工作的 id、其标记、其以后的 State 以及通过 Result.success(outputData)
设置的任何输入数据。利用每个办法的 LiveData,咱们能够通过注册监听器来察看 WorkInfo 的变动,如下所示。
workManager.getWorkInfoByIdLiveData(syncWorker.id)
.observe(getViewLifecycleOwner(), workInfo -> {if (workInfo.getState() != null &&
workInfo.getState() == WorkInfo.State.SUCCEEDED) {Snackbar.make(requireView(),
R.string.work_completed, Snackbar.LENGTH_SHORT)
.show();}
});
并且,WorkManager 2.4.0 及更高版本还反对应用 WorkQuery 对象对已退出队列的作业进行简单查问,WorkQuery 反对按工作的标记、状态和惟一工作名称的组合进行查问,如下所示。
WorkQuery workQuery = WorkQuery.Builder
.fromTags(Arrays.asList("syncTag"))
.addStates(Arrays.asList(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
.addUniqueWorkNames(Arrays.asList("preProcess", "sync")
)
.build();
ListenableFuture<List<WorkInfo>> workInfos = workManager.getWorkInfos(workQuery);
下面代码的作用是查找带有“syncTag”标记、处于 FAILED 或 CANCELLED 状态,且惟一工作名称为“preProcess”或“sync”的所有工作。
3.5 勾销和进行工作
3.5.1 勾销工作
WorkManager 反对勾销对列中的工作,勾销时按工作的 name、id 或与其关联的 tag 来进行勾销,如下所示。
// by id
workManager.cancelWorkById(syncWorker.id);
// by name
workManager.cancelUniqueWork("sync");
// by tag
workManager.cancelAllWorkByTag("syncTag");
WorkManager 会在后盾查看工作的以后 State。如果工作曾经实现,零碎不会执行任何操作。否则工作的状态会更改为 CANCELLED
,之后就不会运行这个工作。
3.5.2 进行工作
正在运行的工作可能因为某些起因而进行运行,次要的起因有以下一些。
- 明确要求勾销它,能够调用 WorkManager.cancelWorkById(UUID) 办法。
- 如果是惟一工作,将 ExistingWorkPolicy 为 REPLACE 的新 WorkRequest 退出到了队列中时,旧的 WorkRequest 会立刻被视为已勾销。
- 增加的工作约束条件不再适宜。
- 零碎出于某种原因批示利用进行工作。
当工作进行后,WorkManager 会立刻调用 ListenableWorker.onStopped()
敞开可能保留的所有资源。
3.6 察看工作的进度
WorkManager 2.3.0 为设置和察看工作的两头进度提供了反对,如果利用在前台运行时,工作器放弃运行状态,那么也能够应用 WorkInfo 的 LiveData Api 向用户显示此信息。ListenableWorker 反对应用 setProgressAsync() 办法来保留两头进度。ListenableWorker 只有在运行时能力察看到和更新进度信息。
3.6.1 更新进度
对于 Java 开发者来说,咱们能够应用 ListenableWorker 或 Worker 的 setProgressAsync() 办法来更新异步过程的进度。耳低于 Kotlin 开发者来说,则能够应用 CoroutineWorker 对象的 setProgress() 扩大函数来更新进度信息。
,如下所示。
Java 写法:
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
public class ProgressWorker extends Worker {
private static final String PROGRESS = "PROGRESS";
private static final long DELAY = 1000L;
public ProgressWorker(
@NonNull Context context,
@NonNull WorkerParameters parameters) {super(context, parameters);
// Set initial progress to 0
setProgressAsync(new Data.Builder().putInt(PROGRESS, 0).build());
}
@NonNull
@Override
public Result doWork() {
try {
// Doing work.
Thread.sleep(DELAY);
} catch (InterruptedException exception) {// ... handle exception}
// Set progress to 100 after you are done doing your work.
setProgressAsync(new Data.Builder().putInt(PROGRESS, 100).build());
return Result.success();}
}
Kotlin 写法:
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Data
import androidx.work.WorkerParameters
import kotlinx.coroutines.delay
class ProgressWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
companion object {
const val Progress = "Progress"
private const val delayDuration = 1L
}
override suspend fun doWork(): Result {val firstUpdate = workDataOf(Progress to 0)
val lastUpdate = workDataOf(Progress to 100)
setProgress(firstUpdate)
delay(delayDuration)
setProgress(lastUpdate)
return Result.success()}
}
3.6.2 察看进度
察看进度能够应用 getWorkInfoBy…() 或 getWorkInfoBy…LiveData() 办法,此办法会返回 WorkInfo 信息,如下所示。
WorkManager.getInstance(getApplicationContext())
// requestId is the WorkRequest id
.getWorkInfoByIdLiveData(requestId)
.observe(lifecycleOwner, new Observer<WorkInfo>() {
@Override
public void onChanged(@Nullable WorkInfo workInfo) {if (workInfo != null) {Data progress = workInfo.getProgress();
int value = progress.getInt(PROGRESS, 0)
// Do something with progress
}
}