WorkManager

https://developer.android.com/topic/libraries/architecture/workmanager/

img

Android新技术之从Service到WorkManager

使用 WorkManager 处理需要立刻执行的后台任务

WorkManager 流程分析和源码解析 | 开发者说·DTalk

If the device is running on API Level 23 or higher, JobScheduler is used. On API Levels 14-22, GcmNetworkManager is chosen if it’s available, otherwise, a custom AlarmManager and BroadcastReciever implementation is used as a fallback.

WorkManager 的特点与适用场景

特点

  1. 保证任务一定会被执行 WorkManager 有自己的数据库,每一个任务的信息与任务状态,都会保存在本地数据库中。所以即使程序没有在运行,或者在设备重启等情况下,WorkManager 依然可以保证任务的执行,只是不保证任务立即被执行。

  2. 合理使用设备资源 在执行很多周期性或非立即执行的任务时,WorkManager 提供我们 API,帮助我们合理利用设备资源,避免不必要的内存,流量,电量等消耗。

适用场景

  1. 可延迟进行的任务 a.满足某些条件才执行的任务,如需要在充电时才执行的任务。 b.用户无感知或可延迟感知的任务,如同步配置信息,同步资源,同步通讯录等。
  2. 定期重复性任务,但时效性要求不高的,如定期 log 上传,数据备份等。
  3. 退出应用后还应继续执行的未完成任务。

WorkManager 的使用

WorkManager 的使用非常简单,分为如下几个步骤:

  1. 创建一个后台任务 Worker。
  2. 定义 WorkRequest,配置运行任务的方式和时间。
  3. 将任务提交给系统处理。
  4. 观察 Worker 的进度或状态。

WorkManager 流程分析与源码解析

这个章节将会从以下几个方面梳理 WorkManager 的流程与源码:

  1. 创建 a. WorkManager的初始化 b. WorkRequest的创建
  2. 非约束条件任务的执行
  3. 带约束条件任务的执行

4.1 创建

首先梳理一下 WorkManager 的初始化过程。

4.1.1. WorkManager 的初始化

WorkManagerInitializer

在默认的情况下,WorkManager 并不是在我们调用 WorkManager.getInstance() 时创建的。通过反编译一下 apk,会发现在 AndroidManifest 文件中注册了名为 WorkManagerInitializer 的 ContentProvider。因此 WorkManager 在 app 冷启动的时候已经被创建。

//AndroidManifest.xml
  <provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:exported="false"
            android:multiprocess="true"
            android:authorities="com.jandroid.multivideo.workmanager-init"
            android:directBootAware="false" />

WorkManagerInitializer 的 onCreate() 方法:


//WorkManagerInitializer
public boolean onCreate() {
       // Initialize WorkManager with the default configuration.
       WorkManager.initialize(getContext(), new Configuration.Builder().build());
       return true;
   }

我们继续看 initialize() 的实现,由于 WorkManager 是个抽象类,真正的构造方法是在他的子类 WorkManagerImpl 实现的:

WorkManagerImpl
//WorkManagerImpl
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
   public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
       synchronized (sLock) {
           if (sDelegatedInstance != null && sDefaultInstance != null) {
               throw new IllegalStateException("WorkManager is already initialized.  Did you "
                       + "try to initialize it manually without disabling "
                       + "WorkManagerInitializer? See "
                       + "WorkManager#initialize(Context, Configuration) or the class level "
                       + "Javadoc for more information.");
           }

           if (sDelegatedInstance == null) {
               context = context.getApplicationContext();
               if (sDefaultInstance == null) {
                   sDefaultInstance = new WorkManagerImpl(
                           context,
                           configuration,
                           new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
               }
               sDelegatedInstance = sDefaultInstance;
           }
       }
   }

此时 sDelegatedInstance 为 null,WorkManager 会先创建一个默认的 WorkManagerTaskExecutor 对象,用来执行 WorkManager 的任务。之后创建一个 WorkManagerImpl 对象:

//WorkManagerImpl
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor) {
        this(context,
                configuration,
                workTaskExecutor,
                context.getResources().getBoolean(R.bool.workmanager_test_configuration));
    }

//WorkManagerImpl
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            boolean useTestDatabase) {
        this(context,
                configuration,
                workTaskExecutor,
                WorkDatabase.create(
                        context.getApplicationContext(),
                        workTaskExecutor.getBackgroundExecutor(),
                        useTestDatabase)
        );
    }
WorkDatabase.create

WorkManager 在此时创建了数据库。WorkDatabase.create() 将任务列表序列化到本地,记录每一个任务的属性,执行条件,执行顺序及执行状态等。从而保证任务在冷启动或硬件重启后,可以根据条件继续执行。接着看 this() 的实现:

//WorkManagerImpl
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            @NonNull WorkDatabase database) {
        Context applicationContext = context.getApplicationContext();
        Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
        List<Scheduler> schedulers = createSchedulers(applicationContext, workTaskExecutor);
        Processor processor = new Processor(
                context,
                configuration,
                workTaskExecutor,
                database,
                schedulers);
        internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
    }

到这里有三个重要的初始化步骤。分别是 createSchedulers() 来根据 Build Version 创建不同的 Schedulers 进行任务调度,Processor() 用来管理 Schedulers 的执行,和 internalInit() 真正的初始化。先看 createSchedulers() 的实现:

createSchedulers
//WorkManagerImpl
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @NonNull
    public List<Scheduler> createSchedulers(
            @NonNull Context context,
            @NonNull TaskExecutor taskExecutor) {

        return Arrays.asList(
                Schedulers.createBestAvailableBackgroundScheduler(context, this),
                // Specify the task executor directly here as this happens before internalInit.
                // GreedyScheduler creates ConstraintTrackers and controllers eagerly.
                new GreedyScheduler(context, taskExecutor, this));
    }

return 一个 Scheduler 数组。其中 GreedyScheduler() 是常驻的,用来执行没有任何约束的非周期性的任务。接下来看 createBestAvailableBackgroundScheduler() 的实现。

//Scheduler
    @NonNull
    static Scheduler createBestAvailableBackgroundScheduler(
            @NonNull Context context,
            @NonNull WorkManagerImpl workManager) {
        Scheduler scheduler;
        if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
            scheduler = new SystemJobScheduler(context, workManager);
            setComponentEnabled(context, SystemJobService.class, true);
            Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
        } else {
            scheduler = tryCreateGcmBasedScheduler(context);
            if (scheduler == null) {
                scheduler = new SystemAlarmScheduler(context);
                setComponentEnabled(context, SystemAlarmService.class, true);
                Logger.get().debug(TAG, "Created SystemAlarmScheduler");
            }
        }
        return scheduler;
    }

这段代码对 build version 进行了判断。若 >=23,则返回 SystemJobScheduler(),即利用 JobScheduler 进行任务管理。<23 的时候先尝试使用 GcmScheduler 进行管理。若无法创建 GcmScheduler 则返回 SystemAlarmScheduler() 使用 AlamManager 进行任务管理。返回的这个 Scheduler 是用来执行周期性,或者有约束性的任务。由此可见,WorkManager 创建了两个 Scheduler,分别为执行非约束非周期性任务的 GreedyScheduler,和执行约束性周期性任务的 SystemJobScheduler/GcmBasedScheduler/SystemAlarmScheduler。

这几种 Scheduler 的构造和执行之后再分析。

Processor

之后初始化 Processor。Processor 存储了 Configuration,TaskExecutor,WorkDatabase,schedulers 等,用来在适当的时机进行任务调度。再来看

internalInit():
//WorkManagerImpl
    private void internalInit(@NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            @NonNull WorkDatabase workDatabase,
            @NonNull List<Scheduler> schedulers,
            @NonNull Processor processor) {

        context = context.getApplicationContext();
        mContext = context;
        mConfiguration = configuration;
        mWorkTaskExecutor = workTaskExecutor;
        mWorkDatabase = workDatabase;
        mSchedulers = schedulers;
        mProcessor = processor;
        mPreferenceUtils = new PreferenceUtils(workDatabase);
        mForceStopRunnableCompleted = false;

        // Checks for app force stops.
        mWorkTaskExecutor.executeOnBackgroundThread(new ForceStopRunnable(context, this));
    }

记录了 Configuration,TaskExecutor,WorkDatabase,schedulers,Processor 等。然后我们看最后一行执行语句,启动了一个 ForceStopRunnable,这个 Runnable 是干什么用的呢?直接看 run() 的实现:

//ForceStopRunnable
    @Override
    public void run() {
        // Migrate the database to the no-backup directory if necessary.
        WorkDatabasePathHelper.migrateDatabase(mContext);
        // Clean invalid jobs attributed to WorkManager, and Workers that might have been
        // interrupted because the application crashed (RUNNING state).
        Logger.get().debug(TAG, "Performing cleanup operations.");
        try {
            boolean needsScheduling = cleanUp();
            if (shouldRescheduleWorkers()) {
                Logger.get().debug(TAG, "Rescheduling Workers.");
                mWorkManager.rescheduleEligibleWork();
                // Mark the jobs as migrated.
                mWorkManager.getPreferenceUtils().setNeedsReschedule(false);
            } else if (isForceStopped()) {
                Logger.get().debug(TAG, "Application was force-stopped, rescheduling.");
                mWorkManager.rescheduleEligibleWork();
            } else if (needsScheduling) {
                Logger.get().debug(TAG, "Found unfinished work, scheduling it.");
                Schedulers.schedule(
                        mWorkManager.getConfiguration(),
                        mWorkManager.getWorkDatabase(),
                        mWorkManager.getSchedulers());
            }
            mWorkManager.onForceStopRunnableCompleted();
        } catch (SQLiteCantOpenDatabaseException
                | SQLiteDatabaseCorruptException
                | SQLiteAccessPermException exception) {
            // ForceStopRunnable is usually the first thing that accesses a database (or an app's
            // internal data directory). This means that weird PackageManager bugs are attributed
            // to ForceStopRunnable, which is unfortunate. This gives the developer a better error
            // message.
            String message =
                    "The file system on the device is in a bad state. WorkManager cannot access "
                            + "the app's internal data store.";
            Logger.get().error(TAG, message, exception);
            throw new IllegalStateException(message, exception);
        }
    }

这段代码的实现细节先不做深究。但是很明显,这个 Runnable 的作用就是在 WorkManager 初始化过程中,发现了未完成的,需要重新执行的任务,或者 app 被强制 kill 的情况下,直接对 Scheduler 进行调度。到此,一个 WorkManager 的初始化流程就完成了。

总结:
  1. WorkManager 的初始化是在 app 冷启动后,由 WorkManagerInitializer 这个 ContentProvider 执行的。

  2. 初始化过程包含了 Configuration,WorkManagerTaskExecutor,WorkDatabase,Schedulers,Processor 等的初始化过程。

  3. Schedulers 有两个。

    (1) GreedyScheduler: 执行没有任何约束的非周期性的任务。

    (2) SystemJobScheduler/GcmBasedScheduler/SystemAlarmScheduler: 执行周期性或者有约束性的任务。优先返回 SystemJobScheduler,在 build version 小于 23 的情况下先尝试返回 GcmBasedScheduler,若返回为空再返回 SystemAlarmScheduler。

  4. 初始化的最后,会根据情况找到需要被执行的任务进行调度执行。

WorkManager 的初始化流程图:

图片

4.1.2. WorkRequest 的创建

总结:

WorkRequest 的创建是为了持有三个重要的成员变量。分别是:

  1. mId: 由 UUID 生成的任务 id。
  2. mWorkSpec: 每个任务的属性。
  3. mTags: 每个任务的标签。

WorkRequest 创建的流程图

图片

4.2 非约束条件任务的执行过程

总结:

  1. 在 WorkManager 执行了 enqueue() 后,创建 WorkContinuationImpl 对象执行 enqueue() 方法。

  2. WorkContinuationImpl 持有的 EnqueueRunnable 对象将任务添加到 db,并交给 Schedulers 去调度。

  3. Schedulers 将任务交给每一个 Scheduler 去处理。在我们的示例中,GreedyScheduler 会先处理这个任务。

  4. GreedyScheduler 经过一系列判断后,调用 WorkManager 的 startWork() 方法执行这种一次性,非延迟,无约束的任务。

  5. WorkManager 持有的 StartWorkRunnable 对象会将任务交给 Processor 去处理,执行 startWork() 方法。

  6. Processor 创建一个 WorkerWrapper 对象,由它去调用 Worker 的 startWork() 方法,执行我们自定义 worker 的任务,并返回相应的 result。

  7. 任务完成后,WorkerWrapper 会根据 result 对任务状态,db 等进行更新,然后 schedule 下一个任务。

WorkManager 任务执行流程图:

图片

4.3 带约束的任务的执行过程

4.3.1. SystemJobScheduler (Build Version >=23)

SystemJobScheduler 使用的是 JobScheduler 来调度执行任务。由于 JobScheduler 的实现过程分析不在本文的讨论范围(总体是binder调用到SystemServer创建的JobSchedulerService进行处理),所以只看 WorkManager 是如何使用 JobScheduler 进行任务调度的。通常 JobScheduler 的使用步骤如下:

  1. 创建 JobService
  2. 配置 JobInfo
  3. 执行

4.3.2. SystemAlarmScheduler (Build Version <23)

SystemAlarmScheduler 使用的是 AlarmManager 来调度执行任务。由于 AlarmManager 的实现过程分析不在本文的讨论范围(也是SystemServer创建的AlarmManagerService),所以只看 WorkManager 是如何使用 AlarmManager 进行任务调度的。反编译 apk 后,在 AndroidManifest 里有如下 receiver 注册:

结语

WorkManager 的使用方法简单,但是在使用时还是要分清场景,适用于可延迟,周期性,必须执行完成的任务。通过对源码的分析,WorkManager 会针对不同 Android 版本的选择适当的策略。细致阅读代码,会发现针对指定的系统版本还有一些小的优化点。WorkManager 目前已经比较稳定,所以如果在场景适合的情况下,推荐使用 WorkManager 来代替原有的任务管理方案。