Android-Jetpack-01-WorkManager

435 阅读5分钟

转载自超低空

在当今移动应用开发的世界中,后台任务的管理与调度变得尤为重要。随着用户对应用性能和响应速度的期望不断提高,开发者们面临着越来越多的挑战——如何在确保流畅体验的同时,有效地处理那些需要在后台执行的复杂任务?此时,Android Jetpack 的 WorkManager 组件应运而生,成为了解决这一难题的利器。

WorkManager 提供了一种灵活且可靠的方式,帮助开发者在各种条件下调度和管理后台任务。无论是定时任务、周期性更新,还是在设备重启后继续执行的任务,WorkManager 都能轻松应对。此外,其强大的特性,如后向兼容性、约束条件、灵活的重试策略和任务持久性等,让它成为 Android 开发中的一项重要工具。

接下来,我们将深入探讨 WorkManager 的核心功能与优势,帮助您了解这一组件如何改变我们处理后台任务的方式。在第一章中,我们将简要介绍 WorkManager 的基本概念及其在 Android 开发中的重要性。让我们开始这段旅程,探索如何利用 WorkManager 提升应用的性能与用户体验。

一、简介

WorkManager 是 Android Jetpack 组件之一,旨在为应用程序提供一种可靠且灵活的后台任务调度解决方案。它被设计用于执行延迟、定时、周期性和一次性的后台任务,而无需开发者自己处理任务的调度和管理。

以下是 WorkManager 的一些主要特点和功能:

  1. 后向兼容性:WorkManager 兼容 Android API 级别 14+(即 Android 4.0及更高版本)的设备,可在广泛的 Android 设备上使用。
  2. 设备适配性:WorkManager 利用了不同版本的设备上可用的最佳后台调度方法,以保证任务的执行效率和能耗优化。它可以选择使用 JobScheduler、Firebase JobDispatcher 或者后台服务 (fallback) 来执行任务。
  3. 约束条件:WorkManager 允许您基于设备状态或者满足特定条件时才运行任务。常见约束条件包括电源连接、网络连接和设备空闲等。
  4. 灵活的重试策略:WorkManager 提供了可配置的重试策略,当任务因错误或约束条件未满足而失败时,它会自动进行重试。
  5. 持久性:WorkManager 可以确保任务即使在设备重启后仍然会执行。它使用持久存储来跟踪任务的状态,并在系统重新启动后恢复任务。
  6. 可观察性和链式任务:WorkManager 支持观察任务的状态,您可以根据需要监听任务的开始、运行和完成等状态。此外,还可以创建任务链,使多个任务按特定顺序执行。
  7. 跨进程和定时任务:WorkManager 提供了一种可跨进程调度的机制,允许应用程序调度后台任务,即使在应用程序关闭或设备重启后仍然有效。它还支持定时任务的调度,可以设置延迟执行时间或周期性地重复执行任务。

二、用法

1 观察Worker的进度或状态

Kotlin
代码解读
复制代码

WorkManager.getInstance(myContext).getWorkInfoByIdLiveData(uploadWorkRequest.id) .observe(lifecycleOwner, Observer { workInfo -> })

2 约束条件

  1. setRequiredNetworkType:网络连接设置
  2. setRequiresBatteryNotLow:是否为低电量时运行 默认false
  3. setRequiresCharging:是否要插入设备(接入电源),默认false
  4. setRequiresDeviceIdle:设备是否为空闲,默认false
  5. 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提供以下策略:

  1. ExistingWorkPolicy.REPLACE:取消现有序列并将其替换为新序列
  2. ExistingWorkPolicy.KEEP:保留现有序列并忽略您的新请求
  3. 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);
    }

回到通用执行流程