Android Jetpack 之使用 WorkManager

Android Jetpack 之使用 WorkManager

简介

WorkManager 是 Android Jetpack 的一部分,它主要用来执行持续的后台工作,比如文件上传、日志上传等。

WorManager 可以执行一次性任务(One time),也可以执行周期性任务(Perodic)。

WorkManager也可以按照执行时间分为 3 类:

  1. 立即运行(Immediate)
  2. 长期运行(Long Running)
  3. 推迟运行(Deferrable)

使用方法

添加依赖

dependencies {
    def work_version = "2.7.1"

    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"

    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:$work_version"

    // optional - RxJava2 support
    implementation "androidx.work:work-rxjava2:$work_version"

    // optional - GCMNetworkManager support
    implementation "androidx.work:work-gcm:$work_version"

    // optional - Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"

    // optional - Multiprocess support
    implementation "androidx.work:work-multiprocess:$work_version"
}

定义 Worker

WorkManager 的所有任务都继承自 Worker 类。

重写 doWork 方法,执行任务,并且返回任务结果。

任务结果分 3 种:

  1. Result.success() 执行成功
  2. Result.failure() 执行失败
  3. Result.retry() 执行失败而且需要重试任务
class UploadWorker(appContext: Context, workerParams: WorkerParameters) :
        Worker(appContext, workerParams) {
    override fun doWork(): Result {
        uploadImages()
        return Result.success()
    }

    private fun uploadImages() {
    }
}

构造 WorkRequest

WorkRequest 是任务请求的基本单元,可以为 WorkRequest 添加参数、设置约束。

WorkRequest 分为 2 种:

  1. OneTimeWorkRequest 一次性工作请求
  2. PeriodicWorkRequest 周期性工作请求

OneTimeWorkRequest

使用 OneTimeWorkRequestBuilder 构造 OneTimeWorkRequest。

class WorkManagerActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val uploadWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
                .build()
...
    }
}

PeriodicWorkRequest

使用 PeriodicWorkRequestBuilder 构造 PeriodicWorkRequest。

PeriodicWorkRequest 在构造时必须传入表示周期的时间维度,至少为 15 分钟。如果少于 15 分钟 PeriodicWorkRequest 会按照 15 分钟处理。

    fun buildPeriodicRequest(): WorkRequest {
        return PeriodicWorkRequestBuilder<UploadWorker>(15L, TimeUnit.MINUTES)
            .build()
    }

PeriodicWorkRequest

public final class PeriodicWorkRequest extends WorkRequest {

    /**
     * The minimum interval duration for {@link PeriodicWorkRequest} (in milliseconds).
     */
    @SuppressLint("MinMaxConstant")
    public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.
    
    ...
    public void setPeriodic(long intervalDuration) {
        if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) {
            Logger.get().warning(TAG, String.format(
                    "Interval duration lesser than minimum allowed value; Changed to %s",
                    MIN_PERIODIC_INTERVAL_MILLIS));
            intervalDuration = MIN_PERIODIC_INTERVAL_MILLIS;
        }
        setPeriodic(intervalDuration, intervalDuration);
    }

设置 InputData

setInputData 为 WorkRequest 增加任务参数。

    fun buildInputDataRequest(): WorkRequest {
        val data = Data.Builder()
            .putString("userName", "Bugra")
            .build()

        return OneTimeWorkRequestBuilder<OneTimeWorker>()
            .setInputData(data)
            .build()
    }

在 doWork 方法可以直接调用 ListenableWorker 的 inputData,使用 getString 读取参数。

class OneTimeWorker(context: Context, params: WorkerParameters) :
    CoroutineWorker(context, params) {
...
    override suspend fun doWork(): Result {
        val userName = inputData.getString("userName")
        ...
    }
}

设置 Constraints

使用 setConstraints 为 WorkRequest 设置参数。比如以下参数:

  1. setRequiredNetworkType 网络状态
  2. setRequiresBatteryNotLow 电量状态
  3. setRequiresCharging 充电状态
    fun buildConstraintsRequest(): WorkRequest {
        val constraints = buildConstraints()
        return OneTimeWorkRequestBuilder<OneTimeWorker>()
            .setConstraints(constraints)
            .build()
    }

    /**
     * constraints
     */
    fun buildConstraints(): Constraints {
        return Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .setRequiresCharging(false)
            .build()
    }

使用 WorkManager 调度任务

构造 WorkRequest 后使用 WorkManager 把 WorkRequest 加入到任务队列。

class WorkManagerActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

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

        val workManager: WorkManager = WorkManager.getInstance(this)
        workManager.enqueue(uploadWorkRequest)
    }
}

监听任务状态

使用 getWorkInfoByIdLiveData 方法从队列获取任务并且转换为可观察的 LiveData,便于观察任务的每个状态,在每种状态的回调做相应操作。

WorkInfo 有如下 6 种状态:

  1. ENQUEUED 入队
  2. RUNNING 运行中
  3. SUCCEEDED 运行成功
  4. FAILED 运行失败
  5. BLOCKED 被阻塞
  6. CANCELLED 取消
    public enum State {

        /**
         * Used to indicate that the {@link WorkRequest} is enqueued and eligible to run when its
         * {@link Constraints} are met and resources are available.
         */
        ENQUEUED,

        /**
         * Used to indicate that the {@link WorkRequest} is currently being executed.
         */
        RUNNING,

        /**
         * Used to indicate that the {@link WorkRequest} has completed in a successful state.  Note
         * that {@link PeriodicWorkRequest}s will never enter this state (they will simply go back
         * to {@link #ENQUEUED} and be eligible to run again).
         */
        SUCCEEDED,

        /**
         * Used to indicate that the {@link WorkRequest} has completed in a failure state.  All
         * dependent work will also be marked as {@code #FAILED} and will never run.
         */
        FAILED,

        /**
         * Used to indicate that the {@link WorkRequest} is currently blocked because its
         * prerequisites haven't finished successfully.
         */
        BLOCKED,

        /**
         * Used to indicate that the {@link WorkRequest} has been cancelled and will not execute.
         * All dependent work will also be marked as {@code #CANCELLED} and will not run.
         */
        CANCELLED;

        /**
         * Returns {@code true} if this State is considered finished.
         *
         * @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and * {@link #CANCELLED}
         *         states
         */
        public boolean isFinished() {
            return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
        }
    }

定义 Worker

class OneTimeWorker(context: Context, params: WorkerParameters) :
    CoroutineWorker(context, params) {
    ...
    override suspend fun doWork(): Result {
        val userName = inputData.getString("userName")
        val random = Random.nextInt(0, 2)
        for (i in 1..10) {
            delay(1000)
            println("delay $i...")
        }
        val outputData = Data.Builder()
            .putInt("random", random)
            .build()
        return if (random == 0 ) {
            Result.Success(outputData)
        } else {
            Result.failure(outputData)
        }
    }
}

观察 Worker

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e(TAG, "onCreate")

        val uploadWorkRequest = buildConstraintsRequest()

        val workManager: WorkManager = WorkManager.getInstance(this)
        workManager.enqueue(uploadWorkRequest)

//        2021-12-28 17:20:33.989 9202-9202 E/WorkManagerActivity: onCreate
//        2021-12-28 17:20:34.048 9202-9202 E/WorkManagerActivity: ENQUEUED
//        2021-12-28 17:20:34.051 9202-9202 E/WorkManagerActivity: RUNNING
//        2021-12-28 17:20:44.176 9202-9202 E/WorkManagerActivity: SUCCEEDED:0
        workManager.getWorkInfoByIdLiveData(uploadWorkRequest.id)
            .observe(this) { workInfo ->
                when (workInfo.state) {
                    WorkInfo.State.ENQUEUED -> {
                        Log.e(TAG, "ENQUEUED")
                    }
                    WorkInfo.State.RUNNING -> {
                        Log.e(TAG, "RUNNING")
                    }
                    WorkInfo.State.SUCCEEDED -> {
                        val outputData = workInfo.outputData
                        val random = outputData.getInt("random", -1)
                        Log.e(TAG, "SUCCEEDED:$random")
                    }
                    WorkInfo.State.FAILED -> {
                        val outputData = workInfo.outputData
                        val random = outputData.getInt("random", -1)
                        Log.e(TAG, "FAILED:$random")
                    }
                    WorkInfo.State.BLOCKED -> {
                        Log.e(TAG, "BLOCKED")
                    }
                    WorkInfo.State.CANCELLED -> {
                        Log.e(TAG, "CANCELLED")
                    }
                }
            }
    }

任务链

WorkManager 可以使用 beginWith、then、combine 等方法构造任务链,保证任务按照指定顺序运行。

先 beginWith 执行 compressRequest,再 then 执行 uploadWorkRequest。

class WorkManagerActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        val compressRequest = OneTimeWorkRequestBuilder<CompressWorker>()
            .build()
        val uploadWorkRequest = OneTimeWorkRequestBuilder<OneTimeWorker>()
            .build()

        val workManager: WorkManager = WorkManager.getInstance(this)
        workManager.beginWith(compressRequest)
            .then(uploadWorkRequest)
            .enqueue()

或者先 beginWith 同时执行 compressRequest、updateLocalRequest 两个任务,等两个任务都执行完毕后,使用 then 执行 uploadWorkRequest。

class WorkManagerActivity : AppCompatActivity() {
...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        val compressRequest = OneTimeWorkRequestBuilder<CompressWorker>()
            .build()

        val updateLocalRequest = OneTimeWorkRequestBuilder<UpdateLocalWorker>().build()
        val uploadWorkRequest = OneTimeWorkRequestBuilder<OneTimeWorker>()
            .build()

        val workManager: WorkManager = WorkManager.getInstance(this)
        workManager.beginWith(listOf(compressRequest, updateLocalRequest))
            .then(uploadWorkRequest)
            .enqueue()

当有更复杂的任务链时,可以使用 WorkContinuation.combine 组合任务链。

compressRequest 和 updateLocalRequest 组成第一条任务链 workContinuation1。

oneTimeRequest 和 uploadWorkRequest 组成第二条任务链 workContinuation2。

使用 WorkContinuation.combine 组合两条任务链,两者都执行完毕后,执行 lastWorkRequest。

需要注意的是,任务链中如果前置任务执行失败,会导致后续无法执行。

class WorkManagerActivity : AppCompatActivity() {
...

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val compressRequest = OneTimeWorkRequestBuilder<CompressWorker>().build()
        val updateLocalRequest = OneTimeWorkRequestBuilder<UpdateLocalWorker>().build()

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

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

        val workManager: WorkManager = WorkManager.getInstance(this)
        val workContinuation1 = workManager.beginWith(compressRequest)
            .then(updateLocalRequest)
        val workContinuation2 = workManager.beginWith(oneTimeRequest)
            .then(uploadWorkRequest)
        WorkContinuation.combine(listOf(workContinuation1, workContinuation2))
            .then(lastWorkRequest)
            .enqueue();

实现原理

WorkManager.getInstance

WorkManager 使用 getInstance 方法得到单例,本质是 WorkManagerImpl 的 getInstance。

class WorkManagerActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e(TAG, "onCreate")
...
        val workManager: WorkManager = WorkManager.getInstance(this)
        workManager.enqueue(uploadWorkRequest)
...
    }
public abstract class WorkManager {
    ...
    public static @NonNull WorkManager getInstance(@NonNull Context context) {
        return WorkManagerImpl.getInstance(context);
    }

WorkManagerImpl.getInstance

WorkManagerImpl 的 getInstance 使用 initialize 初始化 。

public class WorkManagerImpl extends WorkManager {

    public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
       ...
            if (instance == null) {
                ...
                    initialize(
                            appContext,
                            ((Configuration.Provider) appContext).getWorkManagerConfiguration());
                 ...
            }

            return instance;
        }
    }

WorkManagerImpl.initialize

WorkManagerImpl 的 initialize 通过 context、configuration、WorkManagerTaskExecutor 构造 WorkManagerImpl 单例。

public class WorkManagerImpl extends WorkManager {

    public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
        synchronized (sLock) {
            ...
                if (sDefaultInstance == null) {
                    sDefaultInstance = new WorkManagerImpl(
                            context,
                            configuration,
                            new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
                }
                sDelegatedInstance = sDefaultInstance;
            }
        }
    }

WorkManagerImpl 构造

在 WorkManagerImpl 的构造方法中新建了一个 Processor,用来处理任务,构造一个 List 得到任务调度器列表。 然后将 processor 传入 internalInit 初始化。

public class WorkManagerImpl extends WorkManager {

    public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            @NonNull WorkDatabase database) {
        ...
        List<Scheduler> schedulers =
                createSchedulers(applicationContext, configuration, workTaskExecutor);
        Processor processor = new Processor(
                context,
                configuration,
                workTaskExecutor,
                database,
                schedulers);
        internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
    }

WorkManagerImpl.createSchedulers

WorkManagerImpl 的 createSchedulers 构造了 2 个 Scheduler。

  1. createBestAvailableBackgroundScheduler 用来选择合适的后台调度器
  2. GreedyScheduler 用来调度执行没有任何约束的任务
public class WorkManagerImpl extends WorkManager {

    public List<Scheduler> createSchedulers(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @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, configuration, taskExecutor, this));
    }

Schedulers.createBestAvailableBackgroundScheduler

Schedulers 的 createBestAvailableBackgroundScheduler 根据当前手机版本选择不同的调度器,用来在 app 处于后台时调用系统级别的服务执行任务。

  1. 系统版本大于等于 23。使用 SystemJobScheduler,启动 SystemJobService 执行任务。
  2. 系统版本小于 23。通过反射查看手机是否支持 GCM 谷歌云消息服务,如果支持则返回 GCM 调度器 GcmScheduler
  3. 如果系统版本小于 23且不支持 GCM。使用 SystemAlarmScheduler,通过 AlarmManager 执行任务。
public class Schedulers {

    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;
    }

总结

WorkManager 适合做持续性的后台任务,比如日志、图片、文件上传等。

相比普通的后台任务,WorkManager 可以做一次性任务、周期性任务、推迟任务,它也可以在 app 或者系统重启时重新调度任务,设置各种系统约束。WorkManager 还可以将任务组合成任务链的形式,保证任务的调度顺序。

从实现原理上来说,WorkManager 会根据不同的系统版本,选择 JobService、GCM 或者 AlarmMananger 执行任务。

你可能感兴趣的:(Jetpack,android,jetpack,kotlin,android)