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是一个抽象类,它有两种实现,OneTimeWorkRequest和PeriodicWorkRequest,分别对应的是一次性任务和周期性任务。
//只执行一次任务 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();
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; }
原来是在这里选择的