WorkManager原理解析及兼容测试

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,从而开始注册动态广播,监听对应的系统状态(网络,电量等)
    • 添加一个网络条件约束,流程分析

兼容性测试

  • 测试样本
    • 测试机: | 机器编号 | 机器型号 | 系统版本 | | -------- | -------- | -------- | | 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非实时性的消息及指令

转载于:https://juejin.im/post/5cd6779851882569217b9b72

你可能感兴趣的:(WorkManager原理解析及兼容测试)