转载自超低空
在当今移动应用开发的世界中,后台任务的管理与调度变得尤为重要。随着用户对应用性能和响应速度的期望不断提高,开发者们面临着越来越多的挑战——如何在确保流畅体验的同时,有效地处理那些需要在后台执行的复杂任务?此时,Android Jetpack 的 WorkManager 组件应运而生,成为了解决这一难题的利器。
WorkManager 提供了一种灵活且可靠的方式,帮助开发者在各种条件下调度和管理后台任务。无论是定时任务、周期性更新,还是在设备重启后继续执行的任务,WorkManager 都能轻松应对。此外,其强大的特性,如后向兼容性、约束条件、灵活的重试策略和任务持久性等,让它成为 Android 开发中的一项重要工具。
接下来,我们将深入探讨 WorkManager 的核心功能与优势,帮助您了解这一组件如何改变我们处理后台任务的方式。在第一章中,我们将简要介绍 WorkManager 的基本概念及其在 Android 开发中的重要性。让我们开始这段旅程,探索如何利用 WorkManager 提升应用的性能与用户体验。
一、简介
WorkManager 是 Android Jetpack 组件之一,旨在为应用程序提供一种可靠且灵活的后台任务调度解决方案。它被设计用于执行延迟、定时、周期性和一次性的后台任务,而无需开发者自己处理任务的调度和管理。
以下是 WorkManager 的一些主要特点和功能:
- 后向兼容性:WorkManager 兼容 Android API 级别 14+(即 Android 4.0及更高版本)的设备,可在广泛的 Android 设备上使用。
- 设备适配性:WorkManager 利用了不同版本的设备上可用的最佳后台调度方法,以保证任务的执行效率和能耗优化。它可以选择使用 JobScheduler、Firebase JobDispatcher 或者后台服务 (fallback) 来执行任务。
- 约束条件:WorkManager 允许您基于设备状态或者满足特定条件时才运行任务。常见约束条件包括电源连接、网络连接和设备空闲等。
- 灵活的重试策略:WorkManager 提供了可配置的重试策略,当任务因错误或约束条件未满足而失败时,它会自动进行重试。
- 持久性:WorkManager 可以确保任务即使在设备重启后仍然会执行。它使用持久存储来跟踪任务的状态,并在系统重新启动后恢复任务。
- 可观察性和链式任务:WorkManager 支持观察任务的状态,您可以根据需要监听任务的开始、运行和完成等状态。此外,还可以创建任务链,使多个任务按特定顺序执行。
- 跨进程和定时任务:WorkManager 提供了一种可跨进程调度的机制,允许应用程序调度后台任务,即使在应用程序关闭或设备重启后仍然有效。它还支持定时任务的调度,可以设置延迟执行时间或周期性地重复执行任务。
二、用法
1 观察Worker的进度或状态
Kotlin
代码解读
复制代码
WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(uploadWorkRequest.id) .observe(lifecycleOwner, Observer { workInfo -> })
2 约束条件
- setRequiredNetworkType:网络连接设置
- setRequiresBatteryNotLow:是否为低电量时运行 默认false
- setRequiresCharging:是否要插入设备(接入电源),默认false
- setRequiresDeviceIdle:设备是否为空闲,默认false
- setRequiresStorageNotLow:设备可用存储是否不低于临界阈值
3 数据交互
Kotlin
代码解读
复制代码
var data = Data.Builder()
.putString(Constant.WORK_DATA_EVENTKEY, eventType)
.build();
var workRequest = OneTimeWorkRequestBuilder<TestWorker>()
.setInputData(data )
.addTag("TEST") .build()
4 链式任务
Kotlin
代码解读
复制代码
WorkManager.getInstance(this).beginWith(request).then(twoRequest).then(threeRequest).enqueue()
可以使用WorkContinuation.combine()方法连接多个链来创建更复杂的序列。
5 唯一的工作序列
Kotlin
代码解读
复制代码
WorkManager.*getInstance*(MonitorApplication.*getAppContext*()).enqueueUniqueWork("BUS", ExistingWorkPolicy.*KEEP*, busDataWorkRequest);
ExistingWorkPolicy提供以下策略:
- ExistingWorkPolicy.REPLACE:取消现有序列并将其替换为新序列
- ExistingWorkPolicy.KEEP:保留现有序列并忽略您的新请求
- ExistingWorkPolicy.APPEND:将新序列附加到现有序列,在现有序列的最后一个任务完成后运行新序列的第一个任务。
6 一次性任务和周期性任务
scss
代码解读
复制代码
OneTimeWorkRequest busDataWorkRequest = new OneTimeWorkRequest
.Builder(UploadBusDataWork.class)
.setConstraints(constraints)
.addTag("BUS")
.build();
WorkManager.getInstance(MonitorApplication.getAppContext()).enqueueUniqueWork("BUS",
ExistingWorkPolicy.KEEP, busDataWorkRequest);
PeriodicWorkRequest downloadWorkRequest = new PeriodicWorkRequest
.Builder(UpdateConfigWork.class,CONFIG_WORK, TimeUnit.MINUTES)
.setInitialDelay(2, TimeUnit.MINUTES)
.setConstraints(constraints)
.addTag("CONFIG")
.build();
WorkManager.getInstance(MonitorApplication.getAppContext()).enqueueUniquePeriodicWork("CONFIG", ExistingPeriodicWorkPolicy.KEEP,downloadWorkRequest);
三、源码
1,初始化流程
1.1 androidx.work.WorkManagerInitializer
1.2 初始化做了哪些事情?
- 线程池
- 数据库
- Scheduler - 非约束非周期性任务:GreedyScheduler - 执行约束性周期性任务:SystemJobScheduler,SystemAlarmScheduler,GcmScheduler
- Processor:存储了Configuration,TaskExecutor,WorkDatabase,schedulers等
- ForceStopRunnable
1.3 ForceStopRunnable
- 如果需要,将数据库迁移到不备份的目录
- 取消无效的JobScheduler作业/重新调度以前正在运行的作业
- 针对异常中断时WorkManager的状态进行不同的调度操作
通过判断闹钟广播是否存在来确定应用是否被强行停止,若不存在即闹钟Alarm被取消即是强制中断,此时将重新设置一个闹钟Alarm;
Kotlin
代码解读
复制代码
@VisibleForTesting
public boolean isForceStopped() {
PendingIntent pendingIntent = getPendingIntent(mContext, FLAG_NO_CREATE);
if (pendingIntent == null) {
setAlarm(mContext);
return true;
} else {
return false;
}
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static class BroadcastReceiver extends android.content.BroadcastReceiver {
private static final String TAG = Logger.tagWithPrefix("ForceStopRunnable$Rcvr");
@Override
public void onReceive(@NonNull Context context, @Nullable Intent intent) {
// Our alarm somehow got triggered, so make sure we reschedule it. This should really
// never happen because we set it so far in the future.
if (intent != null) {
String action = intent.getAction();
if (ACTION_FORCE_STOP_RESCHEDULE.equals(action)) {
Logger.get().verbose(
TAG,
"Rescheduling alarm that keeps track of force-stops.");
ForceStopRunnable.setAlarm(context);
}
}
}
}
1.4 调度者
1.5 数据库查看
2,执行流程
WorkManager.getInstance(context).enqueue(workrequest)
2.1 EnqueueRunnable
scss
代码解读
复制代码
@Override
public void run() {
try {
if (mWorkContinuation.hasCycles()) {
throw new IllegalStateException(
String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
}
boolean needsScheduling = addToDatabase();
if (needsScheduling) {
// Enable RescheduleReceiver, only when there are Worker's that need scheduling.
final Context context =
mWorkContinuation.getWorkManagerImpl().getApplicationContext();
PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
scheduleWorkInBackground();
}
mOperation.setState(Operation.SUCCESS);
} catch (Throwable exception) {
mOperation.setState(new Operation.State.FAILURE(exception));
}
}
- 循环判断是否当前的任务id是否在它任务链上层被访问过,如果是,那就是存在循环重复执行的情况,就直接抛出异常
- 调用addToDatabase方法,是将WorkSpec添加到数据库,然后调用processContinuation去检查是否有需要调度的任务,先获取当前workContinuation的parents,如果parents不为空并且没有被执行过,那就递归调用先去按序执行parent
- 打开广播接收者,调用scheduleWorkInBackground方法执行work---Schedulers.schedule
2.2 无约束条件执行流程
GreedyScheduler--schedule
Kotlin
代码解读
复制代码
@Override
public void schedule(@NonNull WorkSpec... workSpecs) {
if (mInDefaultProcess == null) {
checkDefaultProcess();
}
if (!mInDefaultProcess) {
Logger.get().info(TAG, "Ignoring schedule request in a secondary process");
return;
}
registerExecutionListenerIfNeeded();
// Keep track of the list of new WorkSpecs whose constraints need to be tracked.
// Add them to the known list of constrained WorkSpecs and call replace() on
// WorkConstraintsTracker. That way we only need to synchronize on the part where we
// are updating mConstrainedWorkSpecs.
Set<WorkSpec> constrainedWorkSpecs = new HashSet<>();
Set<String> constrainedWorkSpecIds = new HashSet<>();
for (WorkSpec workSpec : workSpecs) {
long nextRunTime = workSpec.calculateNextRunTime();
long now = System.currentTimeMillis();
if (workSpec.state == WorkInfo.State.ENQUEUED) {
if (now < nextRunTime) {
// Future work
if (mDelayedWorkTracker != null) {
mDelayedWorkTracker.schedule(workSpec);
}
} else if (workSpec.hasConstraints()) {
if (SDK_INT >= 23 && workSpec.constraints.requiresDeviceIdle()) {
// Ignore requests that have an idle mode constraint.
Logger.get().debug(TAG,
String.format("Ignoring WorkSpec %s, Requires device idle.",
workSpec));
} else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
// Ignore requests that have content uri triggers.
Logger.get().debug(TAG,
String.format("Ignoring WorkSpec %s, Requires ContentUri triggers.",
workSpec));
} else {
constrainedWorkSpecs.add(workSpec);
constrainedWorkSpecIds.add(workSpec.id);
}
} else {
Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id));
mWorkManagerImpl.startWork(workSpec.id);
}
}
}
// onExecuted() which is called on the main thread also modifies the list of mConstrained
// WorkSpecs. Therefore we need to lock here.
synchronized (mLock) {
if (!constrainedWorkSpecs.isEmpty()) {
Logger.get().debug(TAG, String.format("Starting tracking for [%s]",
TextUtils.join(",", constrainedWorkSpecIds)));
mConstrainedWorkSpecs.addAll(constrainedWorkSpecs);
mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
}
}
}
- 1.首先判断是否在主进程,如果不是在主进程执行的调度任务,则直接return,忽略掉该请求。
- 2.遍历方法参数传入的workSpecs数组,计算每个workSpecs的下一次可以被执行时间nextRunTime
- 3.如果当前时间早于nextRunTime,就把他加入到mDelayedWorkTracker中延迟执行
- 4.如果workSpec有限制则根据sdk版本做不同的处理
- 5.如果上述都不符合,则可以开始任务,调用mWorkManagerImpl.startWork(workSpec.id)
通用执行流程
scss
代码解读
复制代码
mWorkManagerImpl.startWork-----StartWorkRunnable(mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);)
WorkerWrapper.run-runWorker()-----mWorker.startWork()
2.3 约束条件执行流程
静态广播
androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy
androidx.work.impl.background.systemalarm.ConstraintProxy$StorageNotLowProxy
androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy
动态广播
Kotlin
代码解读
复制代码
public interface OnConstraintUpdatedCallback {
/**
* Called when a constraint is met.
*
* @param workSpecIds A list of {@link WorkSpec} IDs that may have become eligible to run
*/
void onConstraintMet(@NonNull List<String> workSpecIds);
/**
* Called when a constraint is not met.
*
* @param workSpecIds A list of {@link WorkSpec} IDs that have become ineligible to run
*/
void onConstraintNotMet(@NonNull List<String> workSpecIds);
}
Kotlin
代码解读
复制代码
public interface WorkConstraintsCallback {
/**
* Called when all constraints are met.
*
* @param workSpecIds A list of {@link WorkSpec} IDs that may be eligible to run
*/
void onAllConstraintsMet(@NonNull List<String> workSpecIds);
/**
* Called when all constraints are not met.
*
* @param workSpecIds A list of {@link WorkSpec} IDs that are not eligible to run
*/
void onAllConstraintsNotMet(@NonNull List<String> workSpecIds);
}
回到通用执行流程