Android WorkManager浅谈
作者:XingJimmy 发布时间:2023-03-24 11:26:46
一、原文翻译
WorkManager API 可以很容易的指定可延迟的异步任务。允许你创建任务,并把它交给WorkManager来立即运行或在适当的时间运行。WorkManager根据设备API的级别和应用程序状态等因素来选择适当的方式运行任务。如果WorkManager在应用程序运行时执行你的任务,它会在应用程序进程的新线程中执行。如果应用程序没有运行,WorkManager会根据设备API级别和包含的依赖项选择适当的方式安排后台任务,可能会使用JobScheduler、Firebase JobDispatcher或AlarmManager。你不需要编写设备逻辑来确定设备有哪些功能和选择适当的API;相反,你只要把它交给WorkManager让它选择最佳的方式。
Note:WorkManager适用于需要保证即使应用程序退出系统也能运行任务,比如上传应用数据到服务器。不适用于当应用程序退出后台进程能安全终止工作,这种情况推荐使用ThreadPools。
功能:
基础功能
使用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<Scheduler> 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<Data> 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<ListenableWorker.Result> 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<Result> 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 cancelled
2. 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)
来源:https://juejin.im/post/5c6134f2e51d4563567cbd89


猜你喜欢
- 升级年初了,我们打算升级下apg,这样之后就拥抱下jetpack compose了!!想用comopse有两个必选项agp7.0和kotli
- static关键字static关键词与对象无关。static关键字主要修饰四个部分的内容这里我们主要介绍static修饰属性和修饰方法。1.
- 1.什么是虚函数?用virtual 修饰的成员函数叫虚函数小知识: 没有虚构造函数
- 整理记录 AndroidStudio 把一个 module 项目打包成 jar 包。一、默认自动生成的 jar 包众所周知 android
- 反射机制Java语言提供的一种基础功能,通过反射,我们可以操作这个类或对象,比如获取这个类中的方法、属性和构造方法等。 * :分为JDK动
- Unity中的PostProcessScene:深入解析与实用案例在Unity游戏开发中,我们经常需要对场景进行后处理,以实现更丰富的视觉效
- 先给大家简单介绍下mybatisMyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的J
- 前言使用输入框时产品常常会有一些需求,比如123456789变成123-456-789或者限制一些字符的输入等等。很多时候都是网上搜索就完事
- Java8对集合提供了一种流式计算的方式,这种风格将要处理的元素集合看 作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如
- 在开发中,我们通常需要将从数据库中查询的集合数据转换成类似文件系统一样的树形集合,比如:省市单位,部门机构,书籍分类等TreeNode对象@
- Netty设置为Https访问SSLContextFactorypublic class SSLContextFactory {
- WebService是一种跨编程语言和跨操作系统平台的远程调用技术所谓远程调用,就是一台计算机a上的一个程序可以调用到另外一台计算机b上的一
- 本文实例讲述了Android编程实现滑动按钮功能。分享给大家供大家参考,具体如下:首先效果图:然后是分别建立三个文件,第一个是main.cl
- 大家都用过QQ,肯定有人好奇QQ滑动删除Item的效果是怎样实现的,其实我们使用Swipemenulistview就可以简单的实
- 将一个float型数的整数部分和小数分别输出显示三种方法方一:直接类型转换,再加减,问题是类型转换导致的小数位数精确度变化,目前没找到解决方
- 程序绑定的概念:绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对java来说,绑定分为静态绑定和动态绑定;或者叫做前期绑定和后
- Java%(取模运算)Java的取模运算1.实现算法public static double ramainder(double divide
- 先直接看看效果吧初始情况 点击一个作为标记 再次点击后删除 3.这里还要感谢前辈的代码作为参考,毕竟以前也没有写过关于日历方面的东西,别人确
- VS2019打包WPF安装程序最新教程,使用Visual Studio 2019开发的WPF程序如果想要打包为安装程序,除了在VS2019找
- 相信在做B/S模式的项目时,我们请求server端时通常遇到返回数据的处理,对返回数据的格式处理方式多样,随着JSON的流行,现在很多的项目