WorkManager原理解析及兼容测试
前言:
-
WorkManager的基本功能和特性 可以参考官方文档,也可以参考掘金上的中文翻译,这里就不再累述
-
Demo关键代码参考
-
- 定义Worker,每次执行任务弹出系统通知,并打印log
@TargetApi(Build.VERSION_CODES.O)
@NonNull
@Override
public Result doWork() {
Data inputData = getInputData();
LogHelper.logD("NotificationWorker#doWork, inputData:" + inputData.getKeyValueMap().toString());
String text = inputData.getString("text");
NotificationManager notificationManager = (NotificationManager)getApplicationContext().getSystemService(
Context.NOTIFICATION_SERVICE);
Notification.Builder builder = new Notification.Builder(getApplicationContext());
Notification notification = builder
.setTicker("WorkMgrNtf")
.setContentTitle("WorkMgrNtf")
.setContentText(text)
.setSmallIcon(R.drawable.common_google_signin_btn_icon_dark)
.build();
notificationManager.notify(NTF_ID, notification);
return Result.SUCCESS;
}
复制代码
-
- 生成一个周期性WorkRequest用于定期执行Worker
Data inputData = new Data.Builder()
.putString("text", "PeriodicWorkRequest, ts:" + System.currentTimeMillis())
.build();
Constraints constraint = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
long period = Math.max(5, Integer.parseInt(mEdtPeriod.getText().toString()));
final WorkRequest workRequest = new PeriodicWorkRequest.Builder(NotificationWorker.class, period,
TimeUnit.MINUTES)
.setConstraints(constraint)
.setInputData(inputData)
.build();
mPeriodRequestId = workRequest.getId();
WorkManager.getInstance().enqueue(workRequest);
WorkManager.getInstance().getStatusById(workRequest.getId())
.observeForever(new Observer() {
@Override
public void onChanged(@Nullable WorkStatus workStatus) {
LogHelper.logD(
"OnWorkStatusChanged, requestId:" + workRequest.getId() + ", status:" + workStatus);
}
});
复制代码
核心功能及主流程源码分析
WorkManager初始化
- 业务层无需手动调用初始化代码,apk构建过程中会在androidManifest里注册了一个ContentProvider,如下:
"androidx.work.impl.WorkManagerInitializer"
android:exported="false"
android:multiprocess="true"
android:authorities="com.example.ali.workmgrdemo.workmanager-init"
android:directBootAware="false" />
复制代码
- app进程初始化的时候会自动install这个Provider,执行onCreate方法,从而执行WorkManager的初始化逻辑:
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class WorkManagerInitializer extends ContentProvider {
@Override
public boolean onCreate() {
// Initialize WorkManager with the default configuration.
WorkManager.initialize(getContext(), new Configuration.Builder().build());
return true;
}
......
}
复制代码
- 最终会执行静态方法WorkManagerImpl#initialize,实例化单例
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
synchronized (sLock) {
if (sDelegatedInstance == null) {
context = context.getApplicationContext();
if (sDefaultInstance == null) {
sDefaultInstance = new WorkManagerImpl(
context,
configuration,
new WorkManagerTaskExecutor());
}
sDelegatedInstance = sDefaultInstance;
}
}
}
复制代码
WorkRequest执行流程
OneTimeWorkRequest执行流程
- 首先,Demo里直接访问的WorkManager#getInstance,返回的是一个WorkManagerImpl实例,而WorkManagerImpl的实例可以委派给外部去构造,只不过加了RestrictTo.Scope.LIBRARY_GROUP,业务层没法替换。
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public static void setDelegate(WorkManagerImpl delegate) {
synchronized (sLock) {
sDelegatedInstance = delegate;
}
}
复制代码
- 然后,WorkManagerImpl的主要任务是部分db数据读操作和线程调度,真正的任务操作都封装在具体的Runnable中,譬如:StartWorkRunnable,StopWorkRunnable,EnqueueRunnable,而这些Runnable全部都在同一个backThread中执行
- 任务调度之前,会先写入db持久化,并且判断是否有父任务没有执行(链式执行,上图未体现)
- 满足执行条件,会再次从db读取所有满足条件的WorkSpec
- 选择合适的调度器(取决于系统版本,后面会详细说明)执行周期任务,比如上图中的SystemJobScheduler,这里我们重点关注一次性任务的执行流程
- 对于一次性任务,会通过GreedyScheuler立即执行
- 最终会生成一个WorkerWrapper(实现了Runnable接口),在backThread实例化Worker(我们自定义的NotificationWorker),调用doWork执行业务代码。
PeriodicWorkRequest执行流程
内置的线程池
public class WorkManagerTaskExecutor implements TaskExecutor {
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private final Executor mMainThreadExecutor = new Executor() {
@Override
public void execute(@NonNull Runnable command) {
postToMainThread(command);
}
};
// Avoiding synthetic accessor.
volatile Thread mCurrentBackgroundExecutorThread;
private final ThreadFactory mBackgroundThreadFactory = new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
// Delegate to the default factory, but keep track of the current thread being used.
Thread thread = Executors.defaultThreadFactory().newThread(r);
mCurrentBackgroundExecutorThread = thread;
return thread;
}
};
private final ExecutorService mBackgroundExecutor =
Executors.newSingleThreadExecutor(mBackgroundThreadFactory);
@Override
public void postToMainThread(Runnable r) {
mMainThreadHandler.post(r);
}
@Override
public Executor getMainThreadExecutor() {
return mMainThreadExecutor;
}
@Override
public void executeOnBackgroundThread(Runnable r) {
mBackgroundExecutor.execute(r);
}
@Override
public Executor getBackgroundExecutor() {
return mBackgroundExecutor;
}
@NonNull
@Override
public Thread getBackgroundExecutorThread() {
return mCurrentBackgroundExecutorThread;
}
}
复制代码
- 可以看到,mBackgroundExecutor是一个单线程池,至于为什么要用单线程来执行worker任务,从上面添加任务的流程可以推测原因如下:
- 涉及DB操作,必须在非UI线程执行任务
- 保证任务执行的先后顺序
- 避免DB多线程读写操作引起数据记录不一致
Scheduler任务调度器
Scheduler列表
- 首先,任务调度器是一个size固定为2的列表:
public @NonNull List getSchedulers() {
// Initialized at construction time. So no need to synchronize.
if (mSchedulers == null) {
mSchedulers = Arrays.asList(
Schedulers.createBestAvailableBackgroundScheduler(mContext, this),
new GreedyScheduler(mContext, this));
}
return mSchedulers;
}
复制代码
- GreedyScheduler常驻,主要用来执行一次性任务
- 除了GreedySchefuler常驻之外,另一个Scheduler会根据条件(系统版本,是否安装了PlayService)选择最合适的:
static @NonNull Scheduler createBestAvailableBackgroundScheduler(
@NonNull Context context,
@NonNull WorkManagerImpl workManager) {
Scheduler scheduler;
boolean enableFirebaseJobService = false;
boolean enableSystemAlarmService = false;
if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
scheduler = new SystemJobScheduler(context, workManager);
setComponentEnabled(context, SystemJobService.class, true);
Logger.debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
} else {
try {
scheduler = tryCreateFirebaseJobScheduler(context);
enableFirebaseJobService = true;
Logger.debug(TAG, "Created FirebaseJobScheduler");
} catch (Exception e) {
// Also catches the exception thrown if Play Services was not found on the device.
scheduler = new SystemAlarmScheduler(context);
enableSystemAlarmService = true;
Logger.debug(TAG, "Created SystemAlarmScheduler");
}
}
try {
Class firebaseJobServiceClass = Class.forName(FIREBASE_JOB_SERVICE_CLASSNAME);
setComponentEnabled(context, firebaseJobServiceClass, enableFirebaseJobService);
} catch (ClassNotFoundException e) {
// Do nothing.
}
setComponentEnabled(context, SystemAlarmService.class, enableSystemAlarmService);
return scheduler;
}
复制代码
* 如果apiLevel>=23,选择SystemJobScheduler,内部基于JobScheculer实现任务调度,包括周期任务,约束条件等
* 如果apiLevel<23
* 优先构造FirebaseJobService
* 如果系统没有安装PlayService,会抛出异常,这时候选择SystemAlarmScheduler,其内部是基于AlarmManager实现周期任务
复制代码
周期任务Scheduler
- SystemAlarmScheduler
- 利用AlarmManager生成定时任务,核心代码如下:
if (!workSpec.hasConstraints()) {
Logger.debug(TAG, String.format("Setting up Alarms for %s", workSpecId));
Alarms.setAlarm(mContext, dispatcher.getWorkManager(), workSpecId, triggerAt);
} else {
// Schedule an alarm irrespective of whether all constraints matched.
Logger.debug(TAG,
String.format("Opportunistically setting an alarm for %s", workSpecId));
Alarms.setAlarm(
mContext,
dispatcher.getWorkManager(),
workSpecId,
triggerAt);
复制代码
- SystemJobScheduler
- Job的约束条件配置
JobInfo.Builder builder = new JobInfo.Builder(jobId, mWorkServiceComponent)
.setRequiredNetworkType(jobInfoNetworkType)
.setRequiresCharging(constraints.requiresCharging())
.setRequiresDeviceIdle(constraints.requiresDeviceIdle())
.setExtras(extras);
复制代码
-
- Job的周期参数配置
if (workSpec.isPeriodic()) {
if (Build.VERSION.SDK_INT >= 24) {
builder.setPeriodic(workSpec.intervalDuration, workSpec.flexDuration);
} else {
Logger.debug(TAG,
"Flex duration is currently not supported before API 24. Ignoring.");
builder.setPeriodic(workSpec.intervalDuration);
}
}
复制代码
- FirebaseJobService
- Job的约束条件配置
private int[] getConstraints(WorkSpec workSpec) {
Constraints constraints = workSpec.constraints;
List mConstraints = new ArrayList<>();
if (Build.VERSION.SDK_INT >= 23 && constraints.requiresDeviceIdle()) {
mConstraints.add(Constraint.DEVICE_IDLE);
}
if (constraints.requiresCharging()) {
mConstraints.add(Constraint.DEVICE_CHARGING);
}
if (constraints.requiresBatteryNotLow()) {
Logger.warning(TAG,
"Battery Not Low is not a supported constraint "
+ "with FirebaseJobDispatcher");
}
if (constraints.requiresStorageNotLow()) {
Logger.warning(TAG, "Storage Not Low is not a supported constraint "
+ "with FirebaseJobDispatcher");
}
switch (constraints.getRequiredNetworkType()) {
case NOT_REQUIRED: {
// Don't add a constraint.
break;
}
case CONNECTED: {
mConstraints.add(Constraint.ON_ANY_NETWORK);
break;
}
case UNMETERED: {
mConstraints.add(Constraint.ON_UNMETERED_NETWORK);
break;
}
case NOT_ROAMING: {
Logger.warning(TAG, "Not Roaming Network is not a supported constraint with "
+ "FirebaseJobDispatcher. Falling back to Any Network constraint.");
mConstraints.add(Constraint.ON_ANY_NETWORK);
break;
}
case METERED: {
Logger.warning(TAG, "Metered Network is not a supported constraint with "
+ "FirebaseJobDispatcher. Falling back to Any Network constraint.");
mConstraints.add(Constraint.ON_ANY_NETWORK);
break;
}
}
return toIntArray(mConstraints);
}
复制代码
-
- Job的周期参数配置
private void setExecutionTrigger(Job.Builder builder, WorkSpec workSpec) {
if (Build.VERSION.SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
builder.setTrigger(createContentUriTriggers(workSpec));
} else if (workSpec.isPeriodic()) {
builder.setTrigger(createPeriodicTrigger(workSpec));
builder.setRecurring(true);
} else {
builder.setTrigger(Trigger.NOW);
}
}
复制代码
Worker数据结构
约束条件控制
支持的约束条件
- 网络类型约束,包括以下几种类型:
- 电池状态相关约束,包括:
- 只有正在充电状态下,才能执行
- 处于低电量状态下,限制执行
- 存储状态相关约束,只有一个:
- 可用存储空间偏低的情况下,不允许执行
实现原理
- 每一种约束条件都对应一个ConstraintController,如下图所示:
- 基于系统广播实现的条件约束
-
整体结构图
- ConstraintController:
- 持有所有需要约束的WorkSpec
- 持有ConstaintTracker实例(单例,不同约束类型对应不同实例)
- 实现了ConstraintListener接口,注册到ConstraintTracker监听约束状态的变化
- ConstraintTracker:
- 用于实时追踪约束条件的状态,每一种约束条件都实现了自己的派生类
- 只有当ConstraintController接收到需要约束的WorkSpec,才会调用startTracking,从而开始注册动态广播,监听对应的系统状态(网络,电量等)
- ConstraintController:
-
添加一个网络条件约束,流程分析
-
兼容性测试
- 测试样本
- 测试机: | 机器编号 | 机器型号 | 系统版本 | | -------- | -------- | -------- | | A | Meizu | 5.1 | | B | Google Pixel | 7.0 | | C | Google Pixel | 9.0 |
- 测试指标:
- 在worker线程,是否支持生成系统通知
- 在worker线程,是否支持发起网络请求
- 周期任务在进程被强杀后来是否依然能够自动执行
- 测试数据
- ABC均支持生成系统通知以及发起网络请求
- 周期任务的执行结果:
- 测试机A
10-11 11:28:46.031 3030-3129/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
复制代码
强杀之后任务没有执行,重启app之后,连续执行多个任务(具体第一个任务间隔约150min,任务周期为15min,刚好累计10个任务未执行)
复制代码
10-11 14:02:59.727 9889-9950/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:04.878 9889-9953/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:09.974 9889-9956/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:15.093 9889-9959/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:20.208 9889-9950/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:25.334 9889-9953/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:30.406 9889-9956/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:35.489 9889-9959/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:40.665 9889-9950/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
10-11 14:03:43.777 9889-9953/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539228520797}
复制代码
-
* 测试机B 复制代码
2018-10-10 22:08:47.614 15986-16054/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}
2018-10-10 22:26:36.969 15986-16287/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}
2018-10-10 22:56:17.391 16769-16797/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}
2018-10-10 23:10:18.087 17082-17104/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}
2018-10-10 23:23:42.589 17082-17290/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539180522495}
复制代码
-
* 测试机C 复制代码
2018-10-13 07:37:02.576 16995-17018/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539385621858}
2018-10-13 07:52:02.524 18022-18053/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539385621858}
2018-10-13 08:07:02.582 18554-18584/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539385621858}
2018-10-13 08:22:01.989 18554-19170/com.example.ali.workmgrdemo D/TAG_WORK_MGR: NotificationWorker#doWork, inputData:{text=PeriodicWorkRequest, ts:1539385621858}
复制代码
- 结论分析
- 在worker线程,支持生成系统通知栏及发起网络请求
- 周期任务在app进程非存活状态,能否顺利执行存在版本依赖
- 对于apilevel<23(6.0)的机器,没有办法保证周期任务的顺利执行
- 对于apilevel>=23的机器,可以很好地保证周期任务的顺利执行
- 兼容风险
- 对于apilevel>=23的非原生系统机器,JobScheculer是否依然可以保证周期任务的顺利执行,需要更多的测试数据证明
推荐的使用场景及注意事项
- 用于建立自轮询通道,周期获取轮询消息,用来push非实时性的消息及指令