WorkManager高级概念

2,310 阅读6分钟

WorkManager高级概念

自定义WorkManager配置和初始化

默认情况下,WorkManager会在应用启动时自动配置,使用适合大多数应用的选项。如果需要更多地控制控制WorkManager的任务管理和调度,需要我们自己来初始化并自定义WorkManager的配置。

WorkManager2.1.0及以后的版本

WorkManager2.1.0有多种方法来配置WorkManager。最灵活的方法是提供一个自定义的初始化WorkManager,需要用到on-demand initialization,可以在2.1.0及以后的版本中用到。

请求式初始化

请求式初始化可以在有必要的情况下才创建WorkManager,而不是每次应用启动时。这样就不需要在应用启动时创建WorkManager,提升应用性能。如何使用:

移除默认的初始化

为了提供自己的配置,需要首先移除默认的初始化。需要更新AndroidManifest.xml,用一个合并的规则:tools:node="remove"。(可在androidx.work的aar中找到AndroidManifest.xml,中有provider和receiver的定义)

AndroidManifest.xml的合并可以从这里看到详细内容。

实现Configuration.Provider

使用Application实现Configuration.Provider接口,并且实现getWorkManagerConfiguration()方法。

当需要使用WorkManager时,确保调用了WorkManager.getInstance(context)方法。WorkManager会调用应用自定义的getWorkManagerConfiguration()方法来实现初始化(不需要手动调用WorkManager.initialize())。

public class WorkApplication extends Application implements Configuration.Provider {
    @NonNull
    @Override
    public Configuration getWorkManagerConfiguration() {
        return new Configuration.Builder()
                .setMinimumLoggingLevel(Log.INFO)
                .build();
    }
}

WorkManager2.0.1及以前

针对老版本的WorkManager,有两种初始化选项:

默认初始化

​ 大部分情况下,默认的初始化已经满足要求

WorkManager使用一个自定义的CustomProvider在应用启动时进行初始化。这个是一个内部类:androidx.work.impl.WorkManagerInitializer,并且使用了默认的Configuration。默认的初始化是自动使用的除非显式地禁用。它适合于大多数应用。

自定义初始化

​ 为更精确地控制WorkManager,可以指定自定义的配置。

如果想要控制初始化过程,必须禁用默认的初始化,然后指定自定义的配置。

Configuration cfg = new Configuration.Builder()
                .setExecutor()
                .setInputMergerFactory()
                .setJobSchedulerJobIdRange()
                .setMaxSchedulerLimit()
                .setMinimumLoggingLevel()
                .setTaskExecutor()
                .setWorkerFactory()
                .build();
WorkManager.initialize(this, cfg);

在WorkManager中进行线程处理

基本的实现能满足大部分应用的需求。对于更高级的用法,例如准确地处理被停止的任务,应该学习一下WorkManager中的线程和并发。

WorkManager提供了4种不同类型的任务:

  • Worker是最简单的实现。WorkManager自动用后台线程池去运行
  • CoroutineWorker是Kotlin的推荐实现。CoroutineWorker提供了后台任务暂停功能。默认情况下,CoroutineWorker用一个默认的Dispatcher运行,它可以被自定义。
  • RxWorker是RxJava2的推荐实现。如果已有了大量的异步代码用RxJava实现,应该用RxWorker。
  • ListenableWorker是Worker,CoroutineWorker和RxWorker的基类。它是为那些不使用RxJava2或者必须使用基于回调的异步接口例如FusedLocationProviderClient的那些用户设计 。

Worker中的线程

当使用Worker,WorkManager自动在一个后台线程中调用Worker.doWork()。这个后台线程来自WorkManager的配置指定的线程池。默认情况下,WorkManager建立了一个线程池,但也可以自定义。例如,可以用应用中已有的线程池,或者创建一个单线程的线程池来保证所有的后台任务串行执行,甚至可以指定有多个线程的线程池。为了自定义线程池,确认已经自定义WorkManager的初始化。

WorkManager.initialize(
    context,
    new Configuration.Builder()
        .setExecutor(Executors.newFixedThreadPool(8))
        .build());

串行的执行任务的Worker

public class DownloadWorker extends Worker {

    public DownloadWorker(Context context, WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public Result doWork() {
        for (int i = 0; i < 100; ++i) {
            if(isStopped()){
                break;
            }
            try {
                downloadSynchronously("https://www.google.com");
            } catch (IOException e) {
                return Result.failure();
            }
        }

        return Result.success();
    }

}

其中的Worker.doWork()是同步调用的,应该以阻塞的方法完成全部的后台任务,并且在方法退出时关闭这个任务。如果在doWork()中调用了异步的API并且要返回Result,回调可能会异常。这种情况下,应该尝试使用ListenableWorker。

当正在运行的Worker因为某些原因被终止了,Worker.onStopped()回调会触发。重写这个方法或者调用Worker.isStopped()来检查代码和必要时释放资源。如上述的例子,可以在循环中增加判断。

CoroutineWorker中的线程

对Kotlin使用者,WorkManager提供了对协程的一级支持。需要在构建脚本中引入work-runtime-ktx。应该继承CoroutineWorker而不是继承Worker,它有一些微小的不同。

class CoroutineDownloadWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
	override varl coroutineContext = Dispatchers.IO
    
    override suspend fun doWork(): Result = coroutineScope {
        val jobs = (0 until 100).map {
            async {
                downloadSynchronously("https://www.google.com")
            }
        }

        // awaitAll will throw an exception if a download fails, which CoroutineWorker will treat as a failure
        jobs.awaitAll()
        Result.success()
    }
}

CoroutineWorker.doWork()是一个suspending function。不像Worker,这些代码不是运行在由Configuration指定的线程池中。相反,它默认使用Dispatchers.Default。可以提供自己的CoroutineContext实现自定义。

RxWorker的线程

框架提供了WorkManager和RxJava2的互用性。需要先引入work-rxjava2的依赖。然后,需要继承RxWorker,而不是Worker。最后重写RxWorker.createWork()方法返回一个Single表示执行的结果。

public class RxDownloadWorker extends RxWorker {

    public RxDownloadWorker(Context context, WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Single<Result> createWork() {
        return Observable.range(0, 100)
            .flatMap { download("https://www.google.com") }
            .toList()
            .map { Result.success() };
    }
}

注意,RxWorker.createWork()是在主线程运行,但是返回值是默认在后台线程中监听。可以重写RxWorker.getBackgroundScheduler()来改变监听线程。

流程图

任务的状态变化

  • 任务默认是ENQUEUED状态,然后被存入数据库
  • 任务如果被调度,能够执行,会进入RUNNING状态
  • 如果任务有依赖其他任务,且其他任务未执行完成,则会处于BLOCKED状态;如果依赖的任务执行完成,则会进入ENQUEUED状态,可以被调度执行
  • 如果任务依赖其他任务,而其他任务执行失败,则会直接被标记FAILED
  • 任务在没有执行完成时,都有可能被取消,置于CANCLED状态
  • 成功执行,是SUCCEEDED状态;失败,则为FAILED;运行中也会被取消,则为CANCLED

整体架构图

  • Worker负责业务逻辑处理
  • WorkRequest配置输入输出,延迟,约束等
  • 将任务提交给WorkManager,并存入数据库中
  • Schedulers负责任务调度,根据系统版本有不同的调度器
  • 任务执行完成后,会更新信息到数据库

类图

调度算法

调度流程图

1569635585235

  • 如果API大于等于23,则使用系统的jobscheduler
  • 否则如果支持GCMNetWorkManager,则使用GCMScheduler使用google 商店进行任务管理
  • 否则用AlarmManager和BroadcastReceiver进行任务调度

Android5.0以下使用Alarm和广播的调度流程

在对应版本的work-runtime的aar包中可以找到AndroidManifest.xml,其中定义了provider和receiver。