WorkManager详解

WorkManager详解

前言  

  WorkManager组件是用来管理后台工作任务。Android不是已经有很多管理后台任务的类,比如JobScheduler, AlarmManger;在比如AsyncTask, ThreadPool,WorkManager。WorkManager的优势在哪里,我们为啥要使用WorkManager。我们从几个方面来说明WorkManager的优势。
 
  • WorkManager对比JobScheduler, AlarmManger的优势:AlarmManager是一直存在,适用于类似闹钟那样必须准时唤醒的任务,但是JobScheduler是Android 5.x之后才有。WorkManager的底层实现,会根据你的设备API的情况,自动选用JobScheduler, 或是AlarmManager来实现后台任务。什么?在哪里体现的?耐心等待下文的讲解。

  • WorkManager对比AsyncTask, ThreadPool的优势:WorkManager里面的任务在应用退出之后还可以继续执行。AsyncTask, ThreadPool里面的任务在应用退出之后不会执行。WorkManager适用于那些在应用退出之后任务还需要继续执行的需求(比如应用数据上报服务器的情况),对应那些在应用退出的之后任务也需要终止的情况就需要选择ThreadPool、AsyncTask来实现。

  • WorkManager的出现,为应用程序中那些不需要及时完成的任务,提供统一的解决方案,以便在设备电量和用户体验之间达到一个比较好的平衡。

  • WorkManager能保证任务一定会被执行,即使你的应用程序当前不在运行中,哪怕你的设备重启,任务仍然会在适当的时候被执行。这是因为WorkManager有自己的数据库,关于任务的所有信息和数据都保存在这个数据库中,因此,只要你的任务交给了WorkManager,哪怕你的应用程序彻底退出,或者设备重新启动,WorkManager依然能够保证完成你交给的任务。

兼容范围广

  WorkManager最低能兼容API Level 14,并且不需要你的设备安装有Google Play Services。因此,你不用过于担心兼容性问题,因为API Level 14已经能够兼容几乎100%的设备了。

WorkManager依据设备情况选择方案

  WorkManager能依据设备的情况,选择不同的执行方案。在API Level 23+,通过JobScheduler来完成任务,而在API Level 23以下的设备中,通过AlarmManager和Broadcast Receivers组合完成任务。但无论采用哪种方案,任务最终都是交由Executor来完成。 

  WorkManager不是一种新的工作线程,它的出现不是为了替代其它类型的工作线程。工作线程通常立即运行,并在执行完成后给到用户反馈。而WorkManager不是即时的,它不能保证任务能立即得到执行。

在项目中使用WorkManager

1.在app的build.gradle中添加依赖。

app build.gradle
dependencies {
    implementation rootProject.ext.work_runtime
}

工程根目录下的config.gradle
WorkRuntimeVersion = "2.4.0"
work_runtime = [
            "androidx.work:work-runtime:$WorkRuntimeVersion"
    ]

2.使用Worker定义任务 。

public class UploadLogWorker extends Worker {

    private static final String TAG = "WorkManager";

    public UploadLogWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    /**
     * 耗时的任务,在doWork()方法中执行
     * <p>
     * 有三种类型的返回值:
     * 执行成功返回Result.success()
     * 执行失败返回Result.failure()
     * 需要重新执行返回Result.retry()
     */
    @NonNull
    @Override
    public Result doWork() {
        Log.i("==lwf==", "doWork");
        String inputData = getInputData().getString("input_data");
        // 任务执行完成后返回数据
        Data outputData = new Data.Builder().putString("output_data", inputData).build();
        return Result.success(outputData);
    }
}

3.使用WorkRequest配置任务。通过WorkRequest配置我们的任务何时运行以及如何运行。

  • 设置任务触发条件。例如,我们可以设置在设备处于充电,网络已连接,且电池电量充足的状态下,才触发我们设置的任务
Constraints constraints = new Constraints.Builder()
            .setRequiresCharging(true)//充电
            .setRequiredNetworkType(NetworkType.CONNECTED)//网络已连接
            .setRequiresBatteryNotLow(true)//电池电量充足
            .build();

将该Constraints设置到WorkRequest。WorkRequest是一个抽象类,它有两种实现,OneTimeWorkRequestPeriodicWorkRequest,分别对应的是一次性任务和周期性任务。

 
//只执行一次任务
OneTimeWorkRequest compressionWork = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
             .addTag("UploadTag")
             .setConstraints(constraints)//设置触发条件
             .setInitialDelay(10, TimeUnit.SECONDS)//符合触发条件后,延迟10秒执行
             .setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)//线性重试方案 最大5h 最小10s
             .build();
为任务设置Tag标签。设置Tag后,你就可以通过该抱歉跟踪任务的状态WorkManager.getWorkInfosByTagLiveData(String tag)或者取消任务WorkManager.cancelAllWorkByTag(String tag)。
这里看到重试属性,为何最大5h,最小10s ?原因如下:
public static final long MAX_BACKOFF_MILLIS = 5 * 60 * 60 * 1000; // 5 hours.

public static final long MIN_BACKOFF_MILLIS = 10 * 1000; // 10 seconds.

public void setBackoffDelayDuration(long backoffDelayDuration) {
        if (backoffDelayDuration > MAX_BACKOFF_MILLIS) {
            Logger.get().warning(TAG, "Backoff delay duration exceeds maximum value");
            backoffDelayDuration = MAX_BACKOFF_MILLIS;
        }
        if (backoffDelayDuration < MIN_BACKOFF_MILLIS) {
            Logger.get().warning(TAG, "Backoff delay duration less than minimum value");
            backoffDelayDuration = MIN_BACKOFF_MILLIS;
        }
        this.backoffDelayDuration = backoffDelayDuration;
    }

4.将任务提交给系统。WorkManager.enqueue()方法会将你配置好的WorkRequest交给系统来执行 

WorkManager.getInstance().enqueue(compressionWork);

5.观察任务的状态。

任务在提交给系统后,通过WorkInfo获知任务的状态,WorkInfo包含了任务的id,tag,以及Worker对象传递过来的outputData,以及任务当前的状态。有三种方式可以得到WorkInfo对象。

WorkManager.getWorkInfosByTag()

WorkManager.getWorkInfoById()

WorkManager.getWorkInfosForUniqueWork()

如果你希望能够实时获知任务的状态。这三个方法还有对应的LiveData方法。

WorkManager.getWorkInfosByTagLiveData()

WorkManager.getWorkInfoByIdLiveData()

WorkManager.getWorkInfosForUniqueWorkLiveData()

通过LiveData,我们便可以在任务状态发生变化的时候,收到通知。

WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadWorkRequest.getId()).observe(this, new Observer<WorkInfo>()
        {
            @Override
            public void onChanged(WorkInfo workInfo)
            {
                if (workInfo != null && workInfo.getState() == WorkInfo.State.SUCCEEDED)
                {
                    String outputData = workInfo.getOutputData().getString("output_data");
                    Log.d("==lwf==", "outputData: " + outputData);
                }
            }
        });

6.取消任务。与观察任务类似的,我们也可以根据Id或者Tag取消某个任务,或者取消所有任务。

WorkManager.getInstance(MainActivity.this).cancelAllWork();

7.WorkManager和Worker之间的参数传递。数据的传递通过Data对象来完成。

Data inputData = new Data.Builder().putString("input_data", "Hello World!").build();
        OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
                .setInputData(inputData)
                .build();
WorkManager.getInstance(this).enqueue(uploadWorkRequest);

8.周期任务PeriodicWorkRequest。

PeriodicWorkRequest periodicWorkRequest = new PeriodicWorkRequest
                .Builder(U
ploadLogWorker.class, 15, TimeUnit.MINUTES)
                .setConstraints(constraints)//设置触发条件
                .build();

需要注意的是:周期性任务的间隔时间不能小于15分钟。

原因如下:

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

9.任务链。如果你有一系列的任务需要顺序执行,那么可以利用WorkManager.beginWith().then().then()...enqueue()方法。例如:我们在上传数据之前,需要先对数据进行压缩

WorkManager.getInstance(this).beginWith(compressWorkRequest).then(uploadWorkRequest).enqueue();

假设在上传数据之前,除了压缩数据,还需要更新本地数据。压缩与更新本地数据二者没有顺序,但与上传数据存在先后顺序。

WorkManager.getInstance(this).beginWith(compressWorkRequest, updateLocalWorkRequest).then(uploadWorkRequest).enqueue();

如果有更复杂的任务链,还可以考虑使用WorkContinuation.combine()方法,将任务链组合起来。

OneTimeWorkRequest requestA = new OneTimeWorkRequest.Builder(UploadLogWorker.class).build();
        OneTimeWorkRequest requestB = new OneTimeWorkRequest.Builder(UploadLogWorker.class).build();
        OneTimeWorkRequest requestC = new OneTimeWorkRequest.Builder(UploadLogWorker.class).build();
        //A任务链
        WorkContinuation continuationA = WorkManager.getInstance().beginWith(requestA);
        //B任务链
        WorkContinuation continuationB = WorkManager.getInstance().beginWith(requestB);
        //合并上面两个任务链,在接入requestE任务,入队执行
        List<WorkContinuation> list = new ArrayList<>();
        list.add(continuationA);
        list.add(continuationB);
        WorkContinuation continuation = WorkContinuation.combine(list).then(requestC);
        continuation.enqueue();

 

 采用WorkContinuation.combine()的任务链执行顺序

 

 解析

Workmanager在怎么选择使用的JobScheduler, AlarmManger?

我们再提交任务的时候,是都使用Workmanager.getInstance(Context),看看这个是怎么实现的。

   public static @NonNull WorkManager getInstance(@NonNull Context context) {
        return WorkManagerImpl.getInstance(context);
    }
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
        synchronized (sLock) {
            WorkManagerImpl instance = getInstance();
            if (instance == null) {
                Context appContext = context.getApplicationContext();
                if (appContext instanceof Configuration.Provider) {
                    initialize(
                            appContext,
                            ((Configuration.Provider) appContext).getWorkManagerConfiguration());
                    instance = getInstance(appContext);
                } else {
                    throw new IllegalStateException("WorkManager is not initialized properly.  You "
                            + "have explicitly disabled WorkManagerInitializer in your manifest, "
                            + "have not manually called WorkManager#initialize at this point, and "
                            + "your Application does not implement Configuration.Provider.");
                }
            }

            return instance;
        }
    }

看到这里进行了初始化initialize,为什么开机就能运行呢?

反编译APK,发现AndroidManifest.xml

<provider
            android:name="androidx.work.impl.WorkManagerInitializer"
            android:exported="false"
            android:multiprocess="true"
            android:authorities="com.example.myapplication.workmanager-init"
            android:directBootAware="false" />
注册了WorkManagerInitializer
public class WorkManagerInitializer extends ContentProvider

原来WorkManagerInitializer是一个ContentProvider

public boolean onCreate() {
        // Initialize WorkManager with the default configuration.
        WorkManager.initialize(getContext(), new Configuration.Builder().build());
        return true;
    }
    public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
        WorkManagerImpl.initialize(context, configuration);
    }

原来是这样初始化的,在WorkManagerImpl初始化过程中选择了JobScheduler, AlarmManger方式

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, configuration, workTaskExecutor);
        Processor processor = new Processor(
                context,
                configuration,
                workTaskExecutor,
                database,
                schedulers);
        internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
    }
    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));
    }

 

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

原来是在这里选择的

posted @ 2020-12-24 08:03  心夢無痕  阅读(1873)  评论(0编辑  收藏  举报