前言
Android中执行后台任务有JobScheduler、Loader、Service等方案 ,WorkManager的出现用来替换以上所有的 Android 后台任务方案,为后台任务提供了一套统一的解决方案,保证了api的一致性和稳定性,同时谷歌在开发WorkManager时也考虑到后台任务对电池续航的影响。WorkManager 能保证任务一定会执行,即使用户导航离开屏幕、退出应用或重启设备也不影响。
基本使用
使用 WorkManager 需要继承 Worker
类,后台任务在doWork()
方法中实现
doWork()
方法的返回值Resutl有三种类型:
-
Result.success()
:执行成功。 -
Result.failure()
:执行失败。 -
Result.retry()
:执行失败,重试。
class MainWorker (context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {
companion object { const val TAG = "MainWorker" }
@SuppressLint("RestrictedApi")
override fun doWork(): Result {
Log.d(TAG, "MainWorker doWork: 后台任务执行了")
return Result.Success() // doWork成功 执行任务完毕
}
}
定义任务后,可以使用Constraints设置约束条件,例如定义手机必须联网、必须在充电中、必须是空闲时才执行该后台任务,最后须用 WorkManager 进行调度该任务才能运行。更多进阶使用可以看这里。
fun testBackgroundWork(view: View?) {
//设置约束条件
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 必须是联网中
.setRequiresCharging(true) // 必须是充电中
.setRequiresDeviceIdle(true) // 必须是空闲时
.build()
// 请求对象
val request = OneTimeWorkRequest.Builder(MainWorker::class.java)
.setConstraints(constraints) // Request 关联 约束条件
.build()
// 加入队列
WorkManager.getInstance(this).enqueue(request)
}
源码
1.初始化
上面例子中是通过WorkManager.getInstance获得单例对象,然后再执行后台任务,看一下这个方法的源码。
public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
synchronized (sLock) {
WorkManagerImpl instance = getInstance();//这里不为空,直接返回
if (instance == null) {
Context appContext = context.getApplicationContext();
if (appContext instanceof Configuration.Provider) {
initialize(
appContext,
((Configuration.Provider) appContext).getWorkManagerConfiguration());
instance = getInstance(appContext);
} else {
throw new IllegalStateException("WorkManager is not initialized properly. You "
+ "have explicitly disabled WorkManagerInitializer in your manifest, "
+ "have not manually called WorkManager#initialize at this point, and "
+ "your Application does not implement Configuration.Provider.");
}
}
return instance;
}
}
getInstance方法会调用到WorkManager的实现类WorkManagerImpl中,默认情况下这里第一行代码其实会直接返回,因为getInstance不为空,初始化工作在app启动时就通过ContentProvider完成了。
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:exported="false"
android:multiprocess="true"
android:authorities="com.derry.workmanager.workmanager-init"
android:directBootAware="false" />
public class WorkManagerInitializer extends ContentProvider {
@Override
public boolean onCreate() {
// Initialize WorkManager with the default configuration.
WorkManager.initialize(getContext(), new Configuration.Builder().build());
return true;
}
}
找到编译后的build文件夹中的apk,查看其清单文件可以发现注册了一个provider,这个类的onCreate方法中就调用了WorkManager的初始化方法,我们知道ContentProvider在Application生命周期方法onCreate之前就会初始化,所以WorkManager也在这时就被初始化了。
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(configuration.getTaskExecutor()));
}
sDelegatedInstance = sDefaultInstance;
}
}
}
initialize方法里,configuration.getTaskExecutor()内部返回一个固定线程数量的线程池,创建WorkManagerTaskExecutor用来执行线程池的任务,然后调用WorkManagerImpl的构造方法。
public WorkManagerImpl(
@NonNull Context context,
@NonNull Configuration configuration,
@NonNull TaskExecutor workTaskExecutor,
@NonNull WorkDatabase database) {
Context applicationContext = context.getApplicationContext();
Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
List<Scheduler> schedulers = createSchedulers(applicationContext, workTaskExecutor);
Processor processor = new Processor(
context,
configuration,
workTaskExecutor,
database,
schedulers);
internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
}
WorkManagerImpl构造方法中做了以下几件事
1.创建了Room数据库,Room也是对sqlite的再封装,数据库用来记录每一个后台任务的信息,包括执行顺序 、执行时间等信息;
2.创建schedulers调度器,主要有三种GreedyScheduler、SystemAlarmScheduler、SystemJobScheduler,根据系统版本来选择用哪种;
3.创建Processor处理器,Processor用来管理Schedulers的执行,启动或停止任务;
2.任务入队方法enqueue
进入WorkManagerImpl.enqueue方法,创建一个WorkContinuationImpl对象执行enqueue方法。
public Operation enqueue(
@NonNull List<? extends WorkRequest> workRequests) {
// This error is not being propagated as part of the Operation, as we want the
// app to crash during development. Having no workRequests is always a developer error.
if (workRequests.isEmpty()) {
throw new IllegalArgumentException(
"enqueue needs at least one WorkRequest.");
}
return new WorkContinuationImpl(this, workRequests).enqueue();
}
WorkContinuationImpl.enqueue方法,创建EnqueueRunnable并在后台线程池中执行
public @NonNull Operation enqueue() {
if (!mEnqueued) {
EnqueueRunnable runnable = new EnqueueRunnable(this);
mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
mOperation = runnable.getOperation();
} else {
Logger.get().warning(TAG,
String.format("Already enqueued work ids (%s)", TextUtils.join(", ", mIds)));
}
return mOperation;
}
EnqueueRunnable.run方法,将workSpec添加到数据库并对任务状态进行校验,将RescheduleReceiver注册到AndroidManifest.xml内,在scheduleWorkInBackground方法中执行调度工作
public void run() {
try {
if (mWorkContinuation.hasCycles()) {
throw new IllegalStateException(
String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
}
//将任务信息添加到数据库
boolean needsScheduling = addToDatabase();
if (needsScheduling) {
//启用 RescheduleReceiver,仅当有需要调度的Worker时。
final Context context =
mWorkContinuation.getWorkManagerImpl().getApplicationContext();
//允许将RescheduleReceiver注册到AndroidManifest.xml内
PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
scheduleWorkInBackground();
}
mOperation.setState(Operation.SUCCESS);
} catch (Throwable exception) {
mOperation.setState(new Operation.State.FAILURE(exception));
}
}
3.Schedulers调度器
EnqueueRunnable. scheduleWorkInBackground方法,调用了Schedulers.schedule方法,传入了Configuration, WorkDatabase, Schedulers三个对象
public void scheduleWorkInBackground() {
WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
Schedulers.schedule(
workManager.getConfiguration(),
workManager.getWorkDatabase(),
workManager.getSchedulers());
}
Schedulers.schedule方法,先进行了一系列的数据库操作,主要是查询数据库中未执行的任务,然后根据条件对每个任务进行调度。Schedulers将任务交给每一个Scheduler去处理,GreedyScheduler会先处理这个任务。
public static void schedule(
@NonNull Configuration configuration,
@NonNull WorkDatabase workDatabase,
List<Scheduler> schedulers) {
List<WorkSpec> eligibleWorkSpecs;
...
if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]);
// 交给底层调度器去调度
for (Scheduler scheduler : schedulers) {
scheduler.schedule(eligibleWorkSpecsArray);
}
}
}
GreedyScheduler类,判断是否有约束条件,有则将任务入集合,没有则调用startWork方法。假如有低电量的约束条件,则会在清单文件生成名为BatteryNotLowProxy的receiver,实现原理就是通过监听约束条件变化的广播,然后通过一系列处理,最后也是调用到startWork方法,其他约束条件也是大同小异。
public void schedule(@NonNull WorkSpec... workSpecs) {
...
for (WorkSpec workSpec : workSpecs) {
if (workSpec.state == WorkInfo.State.ENQUEUED
&& !workSpec.isPeriodic()
&& workSpec.initialDelay == 0L
&& !workSpec.isBackedOff()) {
if (workSpec.hasConstraints()) {
...
//有约束条件
constrainedWorkSpecs.add(workSpec);
constrainedWorkSpecIds.add(workSpec.id);
} else {
//无约束条件
mWorkManagerImpl.startWork(workSpec.id);
}
}
}
...
}
WorkManagerImpl.startWork方法,WorkTaskExecutor执行了Runnnable,下面进入 StartWorkRunnable的run()的实现
public void startWork(
@NonNull String workSpecId,
@Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
mWorkTaskExecutor
.executeOnBackgroundThread(
new StartWorkRunnable(this, workSpecId, runtimeExtras));
}
StartWorkRunnable.run方法,将任务的信息交给Processor,由Processor调用startWork()去执行任务
public void run() {
mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
}
4.Processor处理器
processor处理器可以智能地按需安排和执行后台任务
processor.startWork方法,这里会将任务包装成一个WorkWrapper,WorkWrapper也是一个Runnable,然后再次调用WorkTaskExecutor执行Runnnable。
public boolean startWork(@NonNull String id,@Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
WorkerWrapper workWrapper;
synchronized (mLock) {
...
workWrapper =
new WorkerWrapper.Builder(
mAppContext,
mConfiguration,
mWorkTaskExecutor,
this,
mWorkDatabase,
id)
.withSchedulers(mSchedulers)
.withRuntimeExtras(runtimeExtras)
.build();
ListenableFuture<Boolean> future = workWrapper.getFuture();
future.addListener(
new FutureListener(this, id, future),
mWorkTaskExecutor.getMainThreadExecutor());
mEnqueuedWorkMap.put(id, workWrapper);
}
//执行Ruunable
mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
return true;
}
WorkWrapper类的run方法,通过反射获取到ListenableWorker对象也就是Worker类的父类,然后调用ListenableWorker.startWork方法,也就是调用Worker类的startWork方法。
public void run() {
mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);
mWorkDescription = createWorkDescription(mTags);
runWorker();
}
private void runWorker() {
...
//反射获取ListenableWorker对象
if (mWorker == null) {
mWorker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback(
mAppContext,
mWorkSpec.workerClassName,
params);
}
if (trySetRunning()) {
if (tryCheckForInterruptionAndResolve()) {
return;
}
final SettableFuture<ListenableWorker.Result> future = SettableFuture.create();
mWorkTaskExecutor.getMainThreadExecutor()
.execute(new Runnable() {
@Override
public void run() {
try {
Logger.get().debug(TAG, String.format("Starting work for %s",
mWorkSpec.workerClassName));
//这里调用startWork方法
mInnerFuture = mWorker.startWork();
future.setFuture(mInnerFuture);
} catch (Throwable e) {
future.setException(e);
}
}
});
}
Worker.startWork方法,这里即调用到我们自己实现的doWork方法,至此整个流程结束。
public abstract @NonNull Result doWork();
public final @NonNull ListenableFuture<Result> startWork() {
mFuture = SettableFuture.create();
getBackgroundExecutor().execute(new Runnable() {
@Override
public void run() {
try {
Result result = doWork();
mFuture.set(result);
} catch (Throwable throwable) {
mFuture.setException(throwable);
}
}
});
return mFuture;
}
小结
WorkManager是一个很优秀的框架使用方法也很简单,适用于可延迟执行的任务,即使应用程序或设备重新启动也能保证一定会执行。WorkManager会针对不同Android版本的选择不同的执行策略,所以可以的话推荐使用WorkManager替代原有的方案。
以上不足之处请指正,欢迎留言,谢谢。