博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WorkManager浅谈
阅读量:5861 次
发布时间:2019-06-19

本文共 13879 字,大约阅读时间需要 46 分钟。

一、原文翻译

WorkManager API 可以很容易的指定可延迟的异步任务。允许你创建任务,并把它交给WorkManager来立即运行或在适当的时间运行。WorkManager根据设备API的级别和应用程序状态等因素来选择适当的方式运行任务。如果WorkManager在应用程序运行时执行你的任务,它会在应用程序进程的新线程中执行。如果应用程序没有运行,WorkManager会根据设备API级别和包含的依赖项选择适当的方式安排后台任务,可能会使用JobScheduler、Firebase JobDispatcher或AlarmManager。你不需要编写设备逻辑来确定设备有哪些功能和选择适当的API;相反,你只要把它交给WorkManager让它选择最佳的方式。

Note:WorkManager适用于需要保证即使应用程序退出系统也能运行任务,比如上传应用数据到服务器。不适用于当应用程序退出后台进程能安全终止工作,这种情况推荐使用ThreadPools。

功能:

  1. 基础功能
    • 使用WorkManager创建运行在你选择的环境下的单个任务或指定间隔的重复任务
    • WorkManager API使用几个不同的类,有时,你需要继承一些类。
    • Worker 指定需要执行的任务。有一个抽象类Worker,你需要继承并在此处工作。在后台线程同步工作的类。WorkManager在运行时实例化Worker类,并在预先指定的线程调用doWork方法(见Configuration.getExecutor())。此方法同步处理你的工作,意味着一旦方法返回,Worker被视为已经完成并被销毁。如果你需要异步执行或调用异步API,应使用ListenableWorker。如果因为某种原因工作没抢占,相同的Worker实例不会被重用。即每个Worker实例只会调用一次doWork()方法,如果需要重新运行工作单元,需要创建新的Worker。Worker最大10分钟完成执行并ListenableWorker.Result。如果过期,则会被发出信号停止。(Worker的doWork()方法是同步的,方法执行完则结束,不会重复执行,且默认超时时间是10分钟,超过则被停止。)
    • WorkRequest 代表一个独立的任务。一个WorkRequest对象至少指定哪个Worker类应该执行该任务。但是,你还可以给WorkRequest添加详细信息,比如任务运行时的环境。每个WorkRequest有一个自动生成的唯一ID,你可以使用ID来取消排队的任务或获取任务的状态。WorkRequest是一个抽象类,你需要使用它一个子类,OneTimeWorkRequest或PeriodicWorkRequest。
      • WorkRequest.Builder 创建WorkRequest对象的帮助类,你需要使用子类OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder。
      • Constraints(约束) 指定任务执行时的限制(如只有网络连接时)。使用Constraints.Builder创建Constraints对象,并在创建WorkRequest对象前传递给WorkRequest.Builder。
    • WorkManager 排队和管理WorkRequest。将WorkRequest对象传递给WorkManager来将任务添加到队列。WorkManager 使用分散加载系统资源的方式安排任务,同时遵守你指定的约束。
      • WorkManager使用一种底层作业调度服务基于下面的标注
      • 使用JobScheduler API23+
      • 使用AlarmManager + BroadcastReceiver API14-22
    • WorkInfo 包含有关特定任务的信息。WorkManager为每个WorkRequest对象提供一个LiveData。LiveData持有WorkInfo对象,通过观察LiveData,你可以确定任务的当前状态,并在任务完成后获取任何返回的值。

二、源码简单分析

android.arch.work:work-runtime-1.0.0-beta03

WorkerManager的具体实现类是WorkManagerImpl。

WorkManager不同的方法,会创建不同的***Runnable类来执行。

下面是整体的包结构

以EnqueueRunnable为例

@Override    public void run() {        try {            if (mWorkContinuation.hasCycles()) {                throw new IllegalStateException(                        String.format("WorkContinuation has cycles (%s)", mWorkContinuation));            }            boolean needsScheduling = addToDatabase();            if (needsScheduling) {                            final Context context =                        mWorkContinuation.getWorkManagerImpl().getApplicationContext();                PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);                scheduleWorkInBackground();            }            mOperation.setState(Operation.SUCCESS);        } catch (Throwable exception) {            mOperation.setState(new Operation.State.FAILURE(exception));        }    }    /**     * Schedules work on the background scheduler.     */    @VisibleForTesting    public void scheduleWorkInBackground() {        WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();        Schedulers.schedule(                workManager.getConfiguration(),                workManager.getWorkDatabase(),                workManager.getSchedulers());    }复制代码

主要执行在Schedulers类中

/**     * Schedules {@link WorkSpec}s while honoring the {@link Scheduler#MAX_SCHEDULER_LIMIT}.     *     * @param workDatabase The {@link WorkDatabase}.     * @param schedulers   The {@link List} of {@link Scheduler}s to delegate to.     */    public static void schedule(            @NonNull Configuration configuration,            @NonNull WorkDatabase workDatabase,            List
schedulers) { if (schedulers == null || schedulers.size() == 0) { return; } ... if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) { WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]); // Delegate to the underlying scheduler. for (Scheduler scheduler : schedulers) { scheduler.schedule(eligibleWorkSpecsArray); } } }复制代码

下面看下Scheduler的子类

最后会创建WorkerWrapper包装类,来执行我们定义的Worker类。

@WorkerThread    @Override    public void run() {        mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);        mWorkDescription = createWorkDescription(mTags);        runWorker();    }    private void runWorker() {        if (tryCheckForInterruptionAndResolve()) {            return;        }        mWorkDatabase.beginTransaction();        try {            mWorkSpec = mWorkSpecDao.getWorkSpec(mWorkSpecId);            if (mWorkSpec == null) {                Logger.get().error(                        TAG,                        String.format("Didn't find WorkSpec for id %s", mWorkSpecId));                resolve(false);                return;            }            // running, finished, or is blocked.            if (mWorkSpec.state != ENQUEUED) {                resolveIncorrectStatus();                mWorkDatabase.setTransactionSuccessful();                return;            }            // Case 1:            // Ensure that Workers that are backed off are only executed when they are supposed to.            // GreedyScheduler can schedule WorkSpecs that have already been backed off because            // it is holding on to snapshots of WorkSpecs. So WorkerWrapper needs to determine            // if the ListenableWorker is actually eligible to execute at this point in time.            // Case 2:            // On API 23, we double scheduler Workers because JobScheduler prefers batching.            // So is the Work is periodic, we only need to execute it once per interval.            // Also potential bugs in the platform may cause a Job to run more than once.            if (mWorkSpec.isPeriodic() || mWorkSpec.isBackedOff()) {                long now = System.currentTimeMillis();                if (now < mWorkSpec.calculateNextRunTime()) {                    resolve(false);                    return;                }            }            mWorkDatabase.setTransactionSuccessful();        } finally {            mWorkDatabase.endTransaction();        }        // Merge inputs.  This can be potentially expensive code, so this should not be done inside        // a database transaction.        Data input;        if (mWorkSpec.isPeriodic()) {            input = mWorkSpec.input;        } else {            InputMerger inputMerger = InputMerger.fromClassName(mWorkSpec.inputMergerClassName);            if (inputMerger == null) {                Logger.get().error(TAG, String.format("Could not create Input Merger %s",                        mWorkSpec.inputMergerClassName));                setFailedAndResolve();                return;            }            List inputs = new ArrayList<>();            inputs.add(mWorkSpec.input);            inputs.addAll(mWorkSpecDao.getInputsFromPrerequisites(mWorkSpecId));            input = inputMerger.merge(inputs);        }        WorkerParameters params = new WorkerParameters(                UUID.fromString(mWorkSpecId),                input,                mTags,                mRuntimeExtras,                mWorkSpec.runAttemptCount,                mConfiguration.getExecutor(),                mWorkTaskExecutor,                mConfiguration.getWorkerFactory());        // Not always creating a worker here, as the WorkerWrapper.Builder can set a worker override        // in test mode.        if (mWorker == null) {            mWorker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(                    mAppContext,                    mWorkSpec.workerClassName,                    params);        }        if (mWorker == null) {            Logger.get().error(TAG,                    String.format("Could not create Worker %s", mWorkSpec.workerClassName));            setFailedAndResolve();            return;        }        if (mWorker.isUsed()) {            Logger.get().error(TAG,                    String.format("Received an already-used Worker %s; WorkerFactory should return "                            + "new instances",                            mWorkSpec.workerClassName));            setFailedAndResolve();            return;        }        mWorker.setUsed();        // Try to set the work to the running state.  Note that this may fail because another thread        // may have modified the DB since we checked last at the top of this function.        if (trySetRunning()) {            if (tryCheckForInterruptionAndResolve()) {                return;            }            final SettableFuture
future = SettableFuture.create(); // Call mWorker.startWork() on the main thread. mWorkTaskExecutor.getMainThreadExecutor() .execute(new Runnable() { @Override public void run() { try { mInnerFuture = mWorker.startWork(); future.setFuture(mInnerFuture); } catch (Throwable e) { future.setException(e); } } }); // Avoid synthetic accessors. final String workDescription = mWorkDescription; future.addListener(new Runnable() { @Override @SuppressLint("SyntheticAccessor") public void run() { try { // If the ListenableWorker returns a null result treat it as a failure. ListenableWorker.Result result = future.get(); if (result == null) { Logger.get().error(TAG, String.format( "%s returned a null result. Treating it as a failure.", mWorkSpec.workerClassName)); } else { mResult = result; } } catch (CancellationException exception) { // Cancellations need to be treated with care here because innerFuture // cancellations will bubble up, and we need to gracefully handle that. Logger.get().info(TAG, String.format("%s was cancelled", workDescription), exception); } catch (InterruptedException | ExecutionException exception) { Logger.get().error(TAG, String.format("%s failed because it threw an exception/error", workDescription), exception); } finally { onWorkFinished(); } } }, mWorkTaskExecutor.getBackgroundExecutor()); } else { resolveIncorrectStatus(); } }复制代码

这里使用了androidx.work.impl.utils.futures.SettableFuture,并调用了addListener方法,该回调方法会在调用set时执行。

future.addListener(new Runnable() {                @Override                @SuppressLint("SyntheticAccessor")                public void run() {                    try {                        // If the ListenableWorker returns a null result treat it as a failure.                        ListenableWorker.Result result = future.get();                        if (result == null) {                            Logger.get().error(TAG, String.format(                                    "%s returned a null result. Treating it as a failure.",                                    mWorkSpec.workerClassName));                        } else {                            mResult = result;                        }                    } catch (CancellationException exception) {                        // Cancellations need to be treated with care here because innerFuture                        // cancellations will bubble up, and we need to gracefully handle that.                        Logger.get().info(TAG, String.format("%s was cancelled", workDescription),                                exception);                    } catch (InterruptedException | ExecutionException exception) {                        Logger.get().error(TAG,                                String.format("%s failed because it threw an exception/error",                                        workDescription), exception);                    } finally {                        onWorkFinished();                    }                }            }, mWorkTaskExecutor.getBackgroundExecutor());复制代码

下面看下核心的Worker类

@Override    public final @NonNull ListenableFuture
startWork() { mFuture = SettableFuture.create(); getBackgroundExecutor().execute(new Runnable() { @Override public void run() { Result result = doWork(); mFuture.set(result); } }); return mFuture; }复制代码

可见,在调用doWork()后,任务执行完调用了set方法,此时会回调addListener方法。

addListener回调中主要用来判断当前任务的状态,所以如果任务被停止,此处展示捕获的异常信息。 比如调用一个任务的cancel方法,会展示下面的信息。

1. 2019-02-02 15:35:41.682 30526-30542/com.outman.study.workmanagerdemo I/WM-WorkerWrapper: Work [ id=3d775394-e0d7-44e3-a670-c3527a3245ee, tags={ com.outman.study.workmanagerdemo.SimpleWorker } ] was cancelled2.     java.util.concurrent.CancellationException: Task was cancelled.3.         at androidx.work.impl.utils.futures.AbstractFuture.cancellationExceptionWithCause(AbstractFuture.java:1184)4.         at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:514)5.         at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)6.         at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:264)7.         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)8.         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)9.         at java.lang.Thread.run(Thread.java:764)复制代码

以上就是我的简单分析,还有好多没有说到,后面有时间会继续。 有不对的欢迎批评指正。

转载地址:http://izwnx.baihongyu.com/

你可能感兴趣的文章
“***”眼中云计算的“五大漏洞”
查看>>
选择***者的角色---***测试的起点
查看>>
[搬运]python基础教程学习总结_20141003
查看>>
unistd.h/fcntl.h
查看>>
笔记本“***”清除实例
查看>>
zabbix 4.0配置微信报警
查看>>
我的友情链接
查看>>
面对公有云,ITPro该何去何从?
查看>>
HTML框架标签frameset、frame、iframe、noframes
查看>>
spring任务实时调度的几个方法
查看>>
android网络编程关于Socket和http协议
查看>>
多种类型的统计图介绍
查看>>
spring整合redis缓存
查看>>
table td 文字超出显示省略号
查看>>
邮件服务
查看>>
找师傅啊!
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
英文差,如何命名更标准?
查看>>
实战:配置DNS容错
查看>>