WorkManager 初体验

WorkManager 简介 1

其基础是:

  • Api 23+ (6.0 及以上)2: 基于 JobScheduler 3
  • API 14-22(6.0 以下): 自定制用于 的 AlarmManager 4 + BroadcastReceiver
  • 了解一下 WorkManager 的前世今生5
  • 但我们使用的时候并不需要关注使用的 api,交给 WorkManager 即可,由 WorkManager 让它选择最佳选项
    WorkManager 初体验_第1张图片
    18 年 IO 大会发布的 Android Jetpack 组件其中之一,WorkManagerPagingNavigationSlices,兼容 kotlin 语言。其中 WorkManager 一句话概括是:在条件满足(网络状态、电池条件)满足时,管理运行后台 work(即使你的应用没启动也能保证任务能被执行)。

WorkManager 尽可能使用框架 JobScheduler,来帮助优化电池寿命和批处理作业; api 23 以下设备,如果应用使用了 Firebase JobDispatcher 和其它的 Firebase 依赖时,尝试使用 Firebase JobDispatcher,否则,就使用自定义的AlarmManager + BroadcastReceiver 实现。

几个关键的类

一、Worker 指定我们需要执行的任务,继承于 ListenableWorker

  • 数据库使用的是 Room 数据库,用于保存数据
  • doWork() 是运行于后台线程的同步方法
    • 返回值是 Result
    • 成功返回:Result.success()
    • 失败: Result.failure()
    • 需要稍后重试:Result.retry()
  • 抽象方法,doWork() 需要我们实现我们要做的 work
  • 注意 doWork() 中不能更新 UI(需要在主线程中做的事情,Toast 也不行)
    • Can't toast on a thread that has not called Looper.prepare()
  • 一般我们使用 Worker,也是官方推荐的
                          ListenableWorker
                                 |
      +--------------------------+-------------------------+--------------------+
      |                          |                         |                    |
CombineContinuationsWorker  ConstraintTrackingWorker    CoroutineWorker       Worker  
ListenableWorker

所有的 Work 在 ListenableWorker 中完成,ListenableWorkerWorkManager 中是异步执行的

ListenableWorker 在运行时由 WorkerFactory 实例化,配置是 Configuration,在主线程中调用其方法 startWork()

如果一个 work 被制止后,后来又因为某种原因重新启动后,会重新实例化一个 ListenableWorkerstartWork() 在每个 ListenableWorker 中只会被调用一次

一个 ListenableWorker 最大执行时长为 10 min,并会返回 ListenableWorker.Result,如果时间到了, work 将会被停止,ListenableFuture 也会被取消

  • ListenableWorker.Result 有三个子类:
    • Result.Success 表示任务执行成功
    • Result.FAILURE 表示任务执行失败
    • Result.Retry 之后再尝试执行该任务

二、WorkManager 抽象类,实现 WorkManagerImpl 将 WorkRequest 入队和管理 WorkRequest

  • 进行入队(enqueue())等操作、beginWith()等、查询 WorkRequest 信息[getWorkInfoByIdLiveData``getWorkInfoById] 等
  • WorkManager 支持两种 work:OneTimeWorkRequestPeriodicWorkRequest
  • WorkManagerImpl 实例化 WorkManager,为单例模式,初始化在 initialize()方法里,调用在 WorkManagerInitializer 的 onCreate 中,WorkManagerInitializer 继承于 ContentProvider,所以在系统启动的时候就会实例化 WorkManager
//sDelegatedInstance 和 sDefaultInstance 都是 WorkManager 的实例,所以不能共存
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());
        }
        sDelegatedInstance = sDefaultInstance;
    }
}
  • getWorkInfoByIdLiveData 通过 Room 数据库操作拿到 LiveData 数据,最后包裹为 LiveData 类型
  • startWork() 调用 TaskExecutorexecuteOnBackgroundThread(),内部是调用使用 newSingleThreadExecutor 生成的线程的 excute()

三、WorkRequest(抽象类) 代表一个单独的任务

              WorkRequest
                   |
      +--------------------------+
      |                          |                         
OneTimeWorkRequest  PeriodicWorkRequest  
  • PeriodicWorkRequest 的时间不是非常精确,其根据电池优化策略或网络情况,可能会有延迟,如果需要准确的间隔时间来执行任务的话不能使用 PeriodicWorkRequest
  • 一个 WorkRequest 对象可指定一个 Worker 来执行任务
  • 一个 WorkRequest 绑定一个 id(UUID 类型),可用于查询getWorkInfoByIdLiveData()、观察observe()和取消 cancelWorkById()
  • Builder 为抽象内部类,由子类实现
  • 观察主要用到的数据结构是 LiveData
WorkRequest request = new OneTimeWorkRequest.Builder(FooWorker.class).build();
workManager.enqueue(request);
//查询
LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(request.getId());
//观察
status.observe(...);
//取消
workManager.cancelWorkById(request.getId());
  • 其中 Builder 可设置 setConstraints,Constraints 为 work 的条件,指定任务在何时运行(例如,“仅在连接到网络时”)
@ColumnInfo(name = "required_network_type")
private NetworkType mRequiredNetworkType = NOT_REQUIRED;

@ColumnInfo(name = "requires_charging")
private boolean mRequiresCharging;

@ColumnInfo(name = "requires_device_idle")
private boolean mRequiresDeviceIdle;

@ColumnInfo(name = "requires_battery_not_low")
private boolean mRequiresBatteryNotLow;

@ColumnInfo(name = "requires_storage_not_low")
private boolean mRequiresStorageNotLow;

@ColumnInfo(name = "trigger_content_update_delay")
private long mTriggerContentUpdateDelay = -1;

@ColumnInfo(name = "trigger_max_content_delay")
private long  mTriggerMaxContentDelay = -1;
//如当设备空闲时或正在充电等执行 work
Constraints myConstraints = new Constraints.Builder()
    .setRequiresDeviceIdle(true)
    .setRequiresCharging(true)
    // 其他约束参见 Constraints.Builder
     .build();
OneTimeWorkRequest myWork =
                new OneTimeWorkRequest.Builder(CompressWorker.class)
     .setConstraints(myConstraints)
     .build();
四、按照一定的顺序执行 Work
  1. 使用 WorkManager 的 beginWith() 方法以及 WorkContinuationthen()方法,beginWith 返回 WorkContinuation,其方法then()可以执行之后执行的 work,如要执行以下的顺序,可使用以下方法,A-E 都是 Request
/**
            A
            |
      +----------+
      |          |
      B          C
      |
   +----+
   |    |
   D    E   
 
**/
WorkContinuation continuation = workManager.beginWith(A);
continuation.then(B).then(D, E).enqueue();  // A is implicitly enqueued here
continuation.then(C).enqueue();  
  1. 需要注意的是:这种按顺序执行的 Work,需要上一个 Work 执行 ok 才可执行下一个 ok,若之前的 Work 有一个执行失败或被取消,后续的 work 将不再会被执行
五、唯一执行同一个 Work

同一时间内队列里不能存在相同名称的任务,使用 WorkerManager 的 beginUniqueWork(),有 2 个重载方法

    public abstract @NonNull WorkContinuation beginUniqueWork(
            @NonNull String uniqueWorkName,
            @NonNull ExistingWorkPolicy existingWorkPolicy,
            @NonNull OneTimeWorkRequest work);
            
    public abstract @NonNull WorkContinuation beginUniqueWork(
            @NonNull String uniqueWorkName,
            @NonNull ExistingWorkPolicy existingWorkPolicy,
            @NonNull List<OneTimeWorkRequest> work);

其中第三个参数指定入队策略,ExistingWorkPolicy 是枚举类

public enum ExistingWorkPolicy {

    /**
     * If there is existing pending (uncompleted) work with the same unique name, cancel and delete
     * it.  Then, insert the newly-specified work.
     */
    REPLACE,

    /**
     * If there is existing pending (uncompleted) work with the same unique name, do nothing.
     * Otherwise, insert the newly-specified work.
     */
    KEEP,

    /**
     * If there is existing pending (uncompleted) work with the same unique name, append the
     * newly-specified work as a child of all the leaves of that work sequence.  Otherwise, insert
     * the newly-specified work as the start of a new sequence.
     */
    APPEND
}

六、WorkInfo

  • Work 的相关信息,如 Data、State、id 等,其中内部枚举 State,标记了 WorkRequest 现在的状态
public enum State {
    ENQUEUED,
    RUNNING,
    SUCCEEDED,
    FAILED,
    BLOCKED, //如果 WorkRequest 的约束没有通过,那么这个任务就会处于挂起状态
    CANCELLED;
    public boolean isFinished() {
        return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
    }
}
  • Data:类似于我们平常使用的 bundle,用于给 Worker 传递参数

大致我们知道了有关 WorkManager 的几个关键的类

使用三部曲6

  1. 找一个工人分配具体工作
  2. 告诉工人工作时间和环境
  3. 开始工作

0. 引入包

dependencies {
    // WorkManager
    def work_version = "1.0.1"
    implementation "android.arch.work:work-runtime:$work_version"
    
    //WorkManager - kotlin
    implementation "android.arch.work:work-runtime-ktx:$work_version"
    
    // optional - RxJava2 support
    implementation "androidx.work:work-rxjava2:$work_version"
    
    // optional - Test helpers
    androidTestImplementation "android.arch.work:work-testing:$work_version"
}

1、自定义我们的 Worker

继承于 Worker,实现 doWork() 方法,需要注意的是版本 1.0.01.0.1 很不一样,需要注意

//1.0.0 写法
class MyWorker : Worker() {

    override fun doWork(): WorkerResult {
        val str = inputData.getString("demo")
        if (null != str) {
            Log.e("whh", "MyWorker 执行了$str")
            return WorkerResult.SUCCESS
        } else {
            return WorkerResult.FAILURE
        }
    }
}

//1.0.1 写法,数据结构变化了
class UploadWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    override fun doWork(): Result {
        Log.e("whh", "我上传了图片")
        return Result.success()
    }
}

2、how and when work should be run - 构建 WorkRequest,根据需求构建 PeriodicWorkRequestBuilderOneTimeWorkRequest

可以定义 constraints、input to the work、延迟、重试策略等,可参考 Defining your Work Requests

val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
            .build()

3、 将 work 推入 WorkManager,加入到系统中

The exact time that the worker is going to be executed depends on the constraints that are used in your WorkRequest and on system optimizations. WorkManager is designed to give the best possible behavior under these restrictions.
约束条件和系统优化会影响 worker 执行的时间,WorkManager 是为了在这些限制下实现最优表现而设计出来的

WorkManager.getInstance().enqueue(uploadWorkRequest)

Really Easy 吧!

使用起来很简单,剩下的都交给 WorkManager 来进行工作

其他参考:

  • WorkManager完全解析+重构轮询系统
  • WorkManager
  • 新架构组件: WorkManager
  • Android性能模式篇之智能的工作计划

  1. Android 开发者官网 ↩︎

  2. api 等级 ↩︎

  3. Android Jobscheduler 使用 理解JobScheduler机制
    对于满足网络、电量、时间等一定预定条件而触发的任务非常适合 ↩︎

  4. 关于使用 AlarmManager 的注意事项 ↩︎

  5. [译] 从Service到WorkManager
    Service(耗电量大) - JobScheduler(api 21-22 有bug)- JobDispatcher - 需要使用 Google Play Services,可以兼容 Android 5.0 -alamManager (在不同 api 上的时间不准问题,运行任务并没有考虑Doze or App Standby模式;无法指定一些约束 ) ↩︎

  6. Schedule tasks with WorkManager ↩︎

你可能感兴趣的:(Android)