1、前言
WorkManager是合适用于持久性作业的引荐处理方案。假如作业一直要经过运用重启和体系从头发动来调度,便是持久性的作业。因为大多数后台处理操作都是经过持久性作业完结的,因此 WorkManager 是适用于后台处理操作的首要引荐 API。 WorkManager 可处理三种类型的持久性作业:
- 当即履行:有必要当即开端且很快就完结的使命,能够加急。
- 长期运转:运转时刻或许较长(有或许超越 10 分钟)的使命。
- 可延期履行:延期开端而且能够定时运转的预定使命。
2、运用
2.1、引证
implementation "androidx.work:work-runtime:2.7.1" //基础运用
implementation "androidx.work:work-multiprocess:2.7.1" //跨进程时引证
2.2 运用
履行一次性使命
Data data = new Data.Builder().putBoolean("is_test", false).build();
WorkManager.getInstance(this).enqueue(
new OneTimeWorkRequest.Builder(Test.class) // 履行使命一次性使命
.setInputMerger(NewInputMerge.class) // 输入数据兼并战略,这儿并没有用,链式处理时,多个上流履行成果兼并,作为下流输入数据
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES) // 重试战略
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) // 加急处理
.addTag("test").addTag("huahua") // 标识
.setInputData(data) // 输入数据
.setInitialDelay(5, TimeUnit.SECONDS) // 履行延时时刻
.setConstraints(new Constraints.Builder().setRequiresBatteryNotLow(true).build()) // 束缚,部分束缚只对高版别有用
.build()
);
履行周期性使命
WorkManager.getInstance(this).enqueue(
new PeriodicWorkRequest.Builder(Test.class, 2, TimeUnit.HOURS) // 履行周期性使命,周期2小时
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.addTag("test").addTag("huahua")
.setInputData(data)
.setInitialDelay(5, TimeUnit.SECONDS) // 首次履行延时时刻
.setConstraints(new Constraints.Builder().setRequiresBatteryNotLow(true).build())
.build()
);
拥有姓名的使命
WorkManager.getInstance(this).enqueueUniqueWork("test", ExistingWorkPolicy.KEEP,
new OneTimeWorkRequest.Builder(Test.class).build()); // 此种办法会对重复姓名的使命进行处理
监听状况
WorkManager.getInstance(this).getWorkInfosByTagLiveData("test").observe(this,
new Observer<List<WorkInfo>>() {
@Override
public void onChanged(List<WorkInfo> workInfos) {
}
});
定义Work代码
public class Test extends Worker {
public Test(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
Data input = getInputData(); // 获取输入数据
boolean isTest = input.getBoolean("is_test", true);
}
@NonNull
@Override
public Result doWork() {
return Result.success();
}
}
承继使Worker类,完结doWork()办法,此办法是完结使命的主体;doWork()回来的Result会通知 WorkManager 服务作业是否成功,以及作业失利时是否应重试作业。
- Result.success():作业成功完结。
- Result.failure():作业失利。
- Result.retry():作业失利,应根据其重试方针在其他时刻测验。
装备初始化 不同的版别初始化不同,可是都是经过Provider来进行的,2.6之前是WorkManagerInitializer, 2.6之后是InitializationProvider;这儿依照2.7.1的版别来说,老版别有需求留言回复;有两种处理方案
- 移除默许Provider
- 依照Provider流程来进行
依照供给初始化流程处理
- 首先注意一个字符串装备:这个很重要,不建议修改
<string name="androidx_startup" translatable="false">androidx.startup</string>
- 供给完结Initializer的类,这个类是被调用的关键
- 移除默许完结
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" /> // 这表示移除
</provider>
默许完结Initializer类WorkManagerInitializer,运用的默许configuration
public final class WorkManagerInitializer implements Initializer<WorkManager> {
private static final String TAG = Logger.tagWithPrefix("WrkMgrInitializer");
@NonNull
@Override
public WorkManager create(@NonNull Context context) {
Logger.get().debug(TAG, "Initializing WorkManager with default configuration.");
WorkManager.initialize(context, new Configuration.Builder().build()); // 这儿供给Configuration
return WorkManager.getInstance(context);
}
@NonNull
@Override
public List<Class<? extends androidx.startup.Initializer<?>>> dependencies() {
return Collections.emptyList(); // 供给需求履行的其它Initializer
}
}
- 进行注册自定义完结; 在meta-data数据处理,key为你需求调用的初始化类,value有必要为R.sting.androidx_startup这个字符串的值; 如下
<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge" > <meta-data android:name="androidx.work.WorkManagerInitializer" // 这儿为你的完结的Initializer android:value="androidx.startup" /> // 这儿有必要与R.sting.androidx_startup保持一致 </provider>
2.3 重要概念
使命标识
- id : 经过WorkRequest目标getStringId获取,每次增加一次使命,就会得到仅有的id
- name :使命姓名,暂时一次性使命才能够有; 一个使命最多一个姓名,而一个姓名能够对应多个使命; 同姓名使命能够经过定义不同姓名冲突来处理
- tag: 一个使命能够拥有多个tag,一个tag能够对应多个使命
使命类型
- 一次性使命: 仅仅履行一次, 假如成果回来retry时,依照重试退避方针,进行重试,直至成功
- 周期性使命: 依照周期履行,不管成果回来什么状况,每次周期内仅仅履行一次
使命链
运用 WorkManager 创立作业链并将其参加行列。作业链用于指定多个依存使命并定义这些使命的运转顺序。运用WorkManager.beginWith(OneTimeWorkRequest或WorkManager.beginWith(List)回来WorkContinuation实例,然后经过其then(OneTimeWorkRequest)或then(List)增加使命链,最后运用WorkContinuation.enqueue()进行履行。
使命链先增加的使命为后续使命的先决条件, 也便是前面使命成功了后边使命才会履行
重试退避方针
针对worker履行回来Result.retry()时处理战略, 运用办法setBackoffCriteria设置, 有两个目标,战略和延时时刻(允许的最小值,即 10 秒);状况有下面2种
- 线性LINEAR: 每次失利后从头开启使命时刻延时为 延时时刻次数倍(延时时刻 * n)
- EXPONENTIAL: 每次失利后从头开启使命时刻延时为 延时时刻的指数倍(延时时刻 * 2^n)
输入兼并器
针对一次性使命,且在作业链中,父级作业请求的输出将作为子级的输入传入的场景中运用。输入中会存在相同关键字,这时,输入就会存在冲突
WorkManager 供给两种不同类型的InputMerger:
- OverwritingInputMerger:会测验将一切输入中的一切键增加到输出中。假如发生冲突,它会覆盖先前设置的键。
- ArrayCreatingInputMerger: 会测验兼并输入,并在必要时创立数组。
冲突处理方针
调度仅有作业时,发生冲突时要履行的操作。能够经过在将作业参加行列时传递一个枚举来完结此意图。
关于一次性作业,您需求供给一个ExistingWorkPolicy,有4 个选项。
- REPLACE:用新作业替换现有作业。此选项将撤销现有作业。
- KEEP:保留现有作业,并忽略新作业。
- APPEND:将新作业附加到现有作业的末尾。此方针将导致您的新作业到现有作业,在现有作业完结后运转。
现有作业将成为新作业的先决条件。假如现有作业变为
CANCELLED
或FAILED
状况,新作业也会变为CANCELLED
或FAILED
。假如您期望不管现有作业的状况怎么都运转新作业,请改用APPEND_OR_REPLACE
。 - APPEND_OR_REPLACE: 类似于APPEND,不过它并不依靠于先决条件作业状况。**即便现有作业变为
CANCELLED
或FAILED
状况,新作业仍会运转。
束缚条件
使命履行的先决条件,运用Contraints.Builder()进行构建实例。有一下束缚
- NetworkType : 束缚运转作业所需的网络类型。
- BatteryNotLow : 假如设置为 true,那么当设备处于“电量不足形式”时,作业不会运转。
- RequiresCharging : 假如设置为 true,那么作业只能在设备充电时运转。
- DeviceIdle:假如设置为 true,则要求用户的设备有必要处于空闲状况,才能运转作业。在运转批量操作时,此束缚会十分有用;若是不必此束缚,批量操作或许会降低用户设备上正在活跃运转的其他运用的功能。
- StorageNotLow: 假如设置为 true,那么当用户设备上的存储空间不足时,作业不会运转。
还有以下束缚,关于>=24的版别有用:
- setContentUriTriggers办法: 设置触发使命的uri
- setTriggerContentUpdateDelay办法:设置触发履行的延迟时刻
- setTriggerMaxContentDelay办法:设置处分履行的最大延时
3、原理
合理分为几个部分来说
1. 束缚检测: 束缚检测的逻辑以及完结
2. 使命调度: 3种调度器, alarm、greedy、JobScheduler; 用于引发使命履行
3. 使命履行流程 :请求的包装,使命怎么参加调度器,以及调度完结后履行使命概况
这儿有需求了解的一个技能点:SettableFuture,完结了ListenableFuture接口,而且增加了下面接口ListenableFuture。这个类在源码中频频运用
ListenableFuture只要一个办法
void addListener(Runnable listener, Executor executor)
调用了这个办法,这个类才有运用含义;调用之后,表示SettableFuture若完结,则运用executor履行listener
另外一个让我觉得有意思的当地,或许说不同之前future的当地是, SettableFuture未完结Runnable接口,也便是其成果不是来源于自己,来源于调用下面3个办法
public boolean set(@Nullable V value) // 正常设置成果
public boolean setException(Throwable throwable) // 履行成果反常
public boolean setFuture(ListenableFuture<? extends V> future) // 设置履行成果来源,也便是ListenableFuture的履行成果
3.1 束缚检测
类图如下
结合源码剖析,定论有:
- WorkConstraintsTracker类:运用进口,结构器传入WorkConstraintsCallback为成果回调,也能够直接调用办法areAllConstraintsMet来判别是否满意束缚
- ConstraintController类:一种束缚操控器类,经过OnConstraintUpdatedCallback回调实时成果给WorkConstraintsTracker, 经过ConstraintListener与ConstraintTracker联络
- ConstraintTracker:实际束缚值的检测者,ConstraintListener回调实时传递值与ConstraintController;经过setState触发回调
实际检测技能点,也便是ConstraintTracker完结
3.1.1 StorageNotLowTracker类
存储空间的是否低,根据体系播送来处理
Intent.ACTION_DEVICE_STORAGE_OK // 存储空间够用
Intent.ACTION_DEVICE_STORAGE_LOW // 存储空间很低
开始检测状况:
@Override
public Boolean getInitialState() {
Intent intent = mAppContext.registerReceiver(null, getIntentFilter());
if (intent == null || intent.getAction() == null) {
return true;
} else {
switch (intent.getAction()) {
case Intent.ACTION_DEVICE_STORAGE_OK:
return true;
case Intent.ACTION_DEVICE_STORAGE_LOW:
return false;
default:
return null;
}
}
}
实时更新经过接纳播送信息:
public void onBroadcastReceive(Context context, @NonNull Intent intent) {
if (intent.getAction() == null) {
return;
}
switch (intent.getAction()) {
case Intent.ACTION_DEVICE_STORAGE_OK:
setState(true);
break;
case Intent.ACTION_DEVICE_STORAGE_LOW:
setState(false);
break;
}
}
3.1.2 BatteryChargingTracker类
充电状况,同样经过播送来进行处理;
接纳播送根据版别不同而不同如下:
public IntentFilter getIntentFilter() {
IntentFilter intentFilter = new IntentFilter();
if (Build.VERSION.SDK_INT >= 23) {
intentFilter.addAction(BatteryManager.ACTION_CHARGING);
intentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
} else {
intentFilter.addAction(Intent.ACTION_POWER_CONNECTED);
intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
}
return intentFilter;
}
初始状况:
@Override
public Boolean getInitialState() {
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent intent = mAppContext.registerReceiver(null, intentFilter);
if (intent == null) {
Logger.get().error(TAG, "getInitialState - null intent received");
return null;
}
return isBatteryChangedIntentCharging(intent);
}
private boolean isBatteryChangedIntentCharging(Intent intent) {
boolean charging;
if (Build.VERSION.SDK_INT >= 23) {
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
charging = (status == BatteryManager.BATTERY_STATUS_CHARGING
|| status == BatteryManager.BATTERY_STATUS_FULL);
} else {
int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
charging = (chargePlug != 0);
}
return charging;
}
实时状况:
@Override
public void onBroadcastReceive(Context context, @NonNull Intent intent) {
String action = intent.getAction();
if (action == null) {
return;
}
Logger.get().debug(TAG, String.format("Received %s", action));
switch (action) {
case BatteryManager.ACTION_CHARGING:
setState(true);
break;
case BatteryManager.ACTION_DISCHARGING:
setState(false);
break;
case Intent.ACTION_POWER_CONNECTED:
setState(true);
break;
case Intent.ACTION_POWER_DISCONNECTED:
setState(false);
break;
}
}
3.1.3 BatteryNotLowTracker类
电量是否低,同样经过播送实时获取状况
Intent.ACTION_BATTERY_OKAY //电量正常
Intent.ACTION_BATTERY_LOW //电量低
初始状况:
@Override
public Boolean getInitialState() {
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent intent = mAppContext.registerReceiver(null, intentFilter);
if (intent == null) {
Logger.get().error(TAG, "getInitialState - null intent received");
return null;
}
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
float batteryPercentage = level / (float) scale;
return (status == BatteryManager.BATTERY_STATUS_UNKNOWN
|| batteryPercentage > BATTERY_LOW_THRESHOLD); // 这儿是小于15%算是低
}
实时更新:
@Override
public void onBroadcastReceive(Context context, @NonNull Intent intent) {
if (intent.getAction() == null) {
return;
}
Logger.get().debug(TAG, String.format("Received %s", intent.getAction()));
switch (intent.getAction()) {
case Intent.ACTION_BATTERY_OKAY:
setState(true);
break;
case Intent.ACTION_BATTERY_LOW:
setState(false);
break;
}
}
3.1.4 NetworkStateTracker类
网络状况,这个根据版别不同运用也不同
-
大于等于24时, ConnectivityManager.registerDefaultNetworkCallback(NetworkCallback networkCallback) 来进行监听网络状况改变
-
小于24时,经过播送ConnectivityManager.CONNECTIVITY_ACTION来进行处理
初始状况:
NetworkState getActiveNetworkState() {
NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
boolean isConnected = info != null && info.isConnected();
boolean isValidated = isActiveNetworkValidated();
boolean isMetered = ConnectivityManagerCompat.isActiveNetworkMetered(mConnectivityManager);
boolean isNotRoaming = info != null && !info.isRoaming();
return new NetworkState(isConnected, isValidated, isMetered, isNotRoaming);
}
实时状况: 在回调NetworkCallback或许接纳到播送onReceive时,调用getActiveNetworkState得到
3.2 使命调度器
完结Scheduler接口,调度器有3个:
- SystemJobScheduler: 运用JobSceuler来完结使命引发;小于23版别时运用
- SystemAlarmScheduler: 运用Alarm来完结使命引发;大于等于23版别运用
- GreedyScheduler:在进程内直接调用,不同android版别均会建立 其实还有一个androidx.work.impl.background.gcm.GcmScheduler,这个完结需求凭借google的GCM推送服务来完结,这儿用了反射(国内不支持),而且优先于SystemAlarmScheduler
调度器的创立在WorkManagerImpl类
public List<Scheduler> createSchedulers(
@NonNull Context context,
@NonNull Configuration configuration,
@NonNull TaskExecutor taskExecutor) {
return Arrays.asList(
Schedulers.createBestAvailableBackgroundScheduler(context, this), // 能够被体系引发调用手法
new GreedyScheduler(context, configuration, taskExecutor, this)); // 进程内调用手法
}
调度器被调用的逻辑在Schedulers
public static void schedule(Configuration configuration,WorkDatabase workDatabase,List<Scheduler> schedulers) {
if (schedulers == null || schedulers.size() == 0) {
return;
}
WorkSpecDao workSpecDao = workDatabase.workSpecDao();
List<WorkSpec> eligibleWorkSpecsForLimitedSlots;
List<WorkSpec> allEligibleWorkSpecs;
workDatabase.beginTransaction();
try {
eligibleWorkSpecsForLimitedSlots = workSpecDao.getEligibleWorkForScheduling(configuration.getMaxSchedulerLimit());
allEligibleWorkSpecs = workSpecDao.getAllEligibleWorkSpecsForScheduling(MAX_GREEDY_SCHEDULER_LIMIT);
if (eligibleWorkSpecsForLimitedSlots != null && eligibleWorkSpecsForLimitedSlots.size() > 0) {
long now = System.currentTimeMillis();
for (WorkSpec workSpec : eligibleWorkSpecsForLimitedSlots) {
workSpecDao.markWorkSpecScheduled(workSpec.id, now); // 进行插槽状况处理,非默许才会被GreedyScheduler进行调度
}
}
workDatabase.setTransactionSuccessful();
} finally {
workDatabase.endTransaction();
}
if (eligibleWorkSpecsForLimitedSlots != null && eligibleWorkSpecsForLimitedSlots.size() > 0) {
WorkSpec[] eligibleWorkSpecsArray =
new WorkSpec[eligibleWorkSpecsForLimitedSlots.size()];
eligibleWorkSpecsArray =
eligibleWorkSpecsForLimitedSlots.toArray(eligibleWorkSpecsArray);
for (Scheduler scheduler : schedulers) {
if (scheduler.hasLimitedSchedulingSlots()) {
scheduler.schedule(eligibleWorkSpecsArray);
}
}
}
if (allEligibleWorkSpecs != null && allEligibleWorkSpecs.size() > 0) {
WorkSpec[] enqueuedWorkSpecsArray = new WorkSpec[allEligibleWorkSpecs.size()];
enqueuedWorkSpecsArray = allEligibleWorkSpecs.toArray(enqueuedWorkSpecsArray);
for (Scheduler scheduler : schedulers) {
if (!scheduler.hasLimitedSchedulingSlots()) {
scheduler.schedule(enqueuedWorkSpecsArray);
}
}
}
}
-
GreedyScheduler调度器处理使命正在排队,使命最大束缚是200条;其它调度器履行在排队且未被调度器处理的,且和现在调度器现已处理的总和不超越装备中的最大束缚,默许是版别23是10,其它版别20
3.2.1 GreedyScheduler调度器
类图:
经过流程剖析有以下定论:
- 延时经过DelayedWorkTracker来处理,其实是经过Handler机制处理,在主Handler中进行
- 束缚经过WorkConstraintsTracker来进行检测,经过自身完结回调WorkConstraintsCallback来处理
- 满意履行条件的使命,经过WorkManagerImpl进行履行
- 完结ExecutionListener接口,来检测现已完结的使命,避免重复履行
- 有必要在装备的进程中处理,默许为运用主进程
3.2.2 SystemJobScheduler调度器
类图:
- SystemJobInfoConverter进行数据转化JobScheduler需求的入参,以及其它数据相互转化
- SystemJobService负责调用WorkManagerImpl类办法去履行使命;以及使命履行完结的处理
- 束缚由JobScheduler自己内部进行;而这儿只需求调用办法即可
这个调度器比较简单,其完结首要经过了JobScheduler的完结,JobScheduler的完结原理这儿不做介绍
3.2.3 SystemAlarmScheduler调度器
这个调度器完结就比较复杂了;类图:
- SystemAlarmService服务,使命各种状况经过发动此服务来处理
- SystemAlarmDispatcher, 使命各种状况分发处理节点,并回传无使命处理状况,毁掉服务
- CommandHander具体处理各种intent事情,以及内部使命调度完结后收尾(完结了ExecutionListener接口,其含义却不是使命现已履行完结后的处理)
- ConstraintsCommandHandler:处理了束缚状况,运用WorkConstraintsTracker检测其时状况;事情是由*Proxy类播送发动service从而通知的, ConstraintProxyUpdateReceiver操控各种束缚播送是否可用
- Alarms,运用AlramManager机制处理使命延时,并发动服务
- RescheduleReceiver:开机或许时刻事情播送,重发动WorkManager处理剩下使命的;这儿只要静态注册,非一切版别有用
- DelayMetCommandHandler:进行束缚实时更新判别,并在满意履行时经过WorkManagerImpl履行使命;在类图中没有标明其完结了WorkTimer.TimeLimitExceededListener(这个是对使命从调度状况到运转状况的一个操控,10分钟未完结状况改变从头处理)
- 运用了PowerManager.WakeLock确保调度进程中cpu运转
3.3、使命流程
使命流程涉及调度的具体进程,在以下流程图中就会直接从Schedulers办法直接异步履行发动使命;调度进程省略;
3.3.1 enqueue流程
WorkManager调用时,均是先包装成WorkContinuationImpl然后调用其办法enqueue履行;
WorkContinuationImpl: 包含了一个链表,指向了一切依靠的WorkContinuation目标;也记录了使命自身的一些信息和流程状况
流程图
- WorkerWrapper:handleResult办法后续是对不同成果以及使命类别,对使命数据进行相应处理;其中进行了输入数据的兼并;在履行中多个关键点对撤销进行校验处理
- WorkForegroundRunnable:发动前台服务,这个需求在完结Work中供给通知信息(办法getForegroundInfoAsync);假如有加急处理,也是发动前台服务
- ListenableWorker中,经过setProgressAsync能够设置进度数据;成功时,doWork回来的成果中含有成功数据
- EnqueueRunnable:enqueueWorkWithPrerequisites办法中对重复使命依据战略进行了处理
- CancelWorkRunable:iterativelyCancelWorkAndDependents删除了依靠其的使命
- 中间大量运用SettableFuture来让各个Runnable履行等候其履行的先决条件
3.3.2 cancel流程
撤销时,能够经过使命的id、name、tag或许撤销一切;不管是哪种调用,都是经过标识获取满意条件的使命id调集,并进行撤销;其流程也仅仅在id调集查询时不同罢了
流程图
- WorkWrapper进行打断时,其流程也便是使命履行时打断处理,在履行进程中多处对打断进行了检测处理
- 撤销时,对调度、履行中的使命别离进行撤销;一起同步状况到数据库
4、总结
上面仅仅大约介绍了常规运用,以及相关类图、流程,代码细节并没有展现。代码中的一些技能点需求至少会用
- AlarmMananger
- JobScheduler
- 电量低、内存不足、充电状况、网络状况检测以及实时更新
- SettableFuture
- ……
从代码架构上,也有需求去多考虑的当地
- 各个部分的别离笼统
- 播送、服务组件的打开关闭、以及服务的毁掉、使命履行结束后的回调收尾等功能的考量
- 数据库表的规划
- 不管哪个版别,均有两个调度器去履行使命,他们有或许去处理同一个使命,这带来的复杂度处理逻辑以及优势
- ……
假如在此文章中您有所收获,请给作者一个鼓舞,点个赞,谢谢支持
技能改变都很快,但基础技能、理论知识永远都是那些;作者期望在余后的日子中,对常用技能点进行基础知识分享;假如你觉得文章写的不错,请给予重视和点赞;假如文章存在错误,也请多多指教!