前言
WorkManager为后台任务提供了一套统一的解决方案,比如上传/下载/同步服务器等等,而且兼容性好,API 14+就可以使用,传统的后台任务比如说Service,如果没有被恰当的使用,会耗电量非常大。WorkManager的出现,则是为应用程序中那些不需要及时完成的任务,提供统一的解决方案,以便在设备电量和用户体验之间达到一个比较好的平衡。
为什么选择WorkManager
1.针对不需要及时完成的任务:发送应用程序日志,同步应用程序数据,备份用户数据等。站在业务的角度,这些任务都不需要立即完成,如果我们自己来管理这些任务,逻辑可能会非常复杂,若API使用不恰当,可能会消耗大量电量。
2.保证任务一定会被执行:WorkManager能保证任务一定会被执行,即使你的应用程序当前不在运行中,哪怕你的设备重启,任务仍然会在适当的时候被执行。这是因为WorkManager有自己的数据库,关于任务的所有信息和数据都保存在这个数据库中,因此,只要你的任务交给了WorkManager,哪怕你的应用程序彻底退出,或者设备重新启动,WorkManager依然能够保证完成你交给的任务。
初始化原理
使用就不细说了,感兴趣的去搜一下,介绍的很详细。这里直接来看原理:
// 约束条件,必须满足我的条件,才能执行后台任务 (在连接网络,插入电源 并且 处于空闲时) 内部做了电量优化(Android App 不耗电)
Constraints myConstraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 网络链接中...
.setRequiresCharging(true) // 充电中..
.setRequiresDeviceIdle(true) // 空闲时.. (没有玩游戏)
.build();
// 请求对象
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(MainWorkManager2.class)
.setConstraints(myConstraints) // 约束条件的执行
.build();
WorkManager.getInstance(this) // 初始化工作源码
.enqueue(request); // 加入队列执行
看看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;
}
}
}
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);
//database就是数据库
internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
仔细分析下,在初始化时,做了如下:
1.创建了数据库room
2.创建了任务池WorkManagerTaskExecutor
3.创建了一个新的进程processor
4.internalInit()中,就是一些参数的传递,还有一个最重要的一点/ Checks for app force stops. 检测被停止的事情(例如:你的手机关机,死机了 ...)内部做容错处理,因为你的手机,发生了意外,内部必须知道
加入任务队列
WorkManager.getInstance(this) // 初始化工作源码
.enqueue(request); // 加入队列执行
看看enqueue做了啥事。
public Operation enqueue(
@NonNull List<? extends WorkRequest> workRequests) {
if (workRequests.isEmpty()) {
throw new IllegalArgumentException(
"enqueue needs at least one WorkRequest.");
}
return new WorkContinuationImpl(this, workRequests).enqueue();
}
很简单,返回了一个WorkContinuationImp实例并调用他的enqueue()方法。
接着往下看
public @NonNull Operation enqueue() {
//创建一个runnable 并在后台运行。
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方法,去瞧瞧,
@Override
public void run() {
try {
``````省略,都是一些异常处理和条件判断
//核心代码,安排工作
scheduleWorkInBackground();
````
}
public void scheduleWorkInBackground() {
WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
Schedulers.schedule(
//配置
workManager.getConfiguration(),
//工作数据库
workManager.getWorkDatabase(),
//调度信息
workManager.getSchedulers());
}
}
经过上面的调用,可以获取到在初始化的配置,数据库。看看schedule里面做了啥事。
public static void schedule(
@NonNull Configuration configuration,
@NonNull WorkDatabase workDatabase,
List<Scheduler> schedulers) {
if (schedulers == null || schedulers.size() == 0) {
return;
}
//拿到数据库操作对象,用来操作数据库的。
WorkSpecDao workSpecDao = workDatabase.workSpecDao();
List<WorkSpec> eligibleWorkSpecs;
//开启事务
workDatabase.beginTransaction();
try {
eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(
configuration.getMaxSchedulerLimit());
if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
long now = System.currentTimeMillis();
// Mark all the WorkSpecs as scheduled.
// Calls to Scheduler#schedule() could potentially result in more schedules
// on a separate thread. Therefore, this needs to be done first.
for (WorkSpec workSpec : eligibleWorkSpecs) {
workSpecDao.markWorkSpecScheduled(workSpec.id, now);
}
}
workDatabase.setTransactionSuccessful();
} finally {
workDatabase.endTransaction();
}
//事务开启成功后,就遍历取出去处理
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);
}
}
}
在调用schdule时,会选择合适的调度器去调度,有三种,里面的处理逻辑不同,
1.GreedyScheduler 2.SystemAlarmScheduler 3.SystemJobScheduler。
这里以GreedyScheduler 为例,从名字可以看出,一定会去调度。
public void schedule(@NonNull WorkSpec... workSpecs) {
if (mIsMainProcess == null) {
// The default process name is the package name.
mIsMainProcess = TextUtils.equals(mContext.getPackageName(), getProcessName());
}
if (!mIsMainProcess) {
Logger.get().info(TAG, "Ignoring schedule request in non-main process");
return;
}
registerExecutionListenerIfNeeded();
// Keep track of the list of new WorkSpecs whose constraints need to be tracked.
// Add them to the known list of constrained WorkSpecs and call replace() on
// WorkConstraintsTracker. That way we only need to synchronize on the part where we
// are updating mConstrainedWorkSpecs.
List<WorkSpec> constrainedWorkSpecs = new ArrayList<>();
List<String> constrainedWorkSpecIds = new ArrayList<>();
for (WorkSpec workSpec : workSpecs) {
if (workSpec.state == WorkInfo.State.ENQUEUED
&& !workSpec.isPeriodic()
&& workSpec.initialDelay == 0L
&& !workSpec.isBackedOff()) {
//如果有约束条件
if (workSpec.hasConstraints()) {
if (SDK_INT >= 23 && workSpec.constraints.requiresDeviceIdle()) {
// Ignore requests that have an idle mode constraint.
Logger.get().debug(TAG,
String.format("Ignoring WorkSpec %s, Requires device idle.",
workSpec));
} else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
// Ignore requests that have content uri triggers.
Logger.get().debug(TAG,
String.format("Ignoring WorkSpec %s, Requires ContentUri triggers.",
workSpec));
} else {
constrainedWorkSpecs.add(workSpec);
constrainedWorkSpecIds.add(workSpec.id);
}
//无约束条件
} else {
//这就开始工作了
Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id));
mWorkManagerImpl.startWork(workSpec.id);
}
}
}
// onExecuted() which is called on the main thread also modifies the list of mConstrained
// WorkSpecs. Therefore we need to lock here.
synchronized (mLock) {
if (!constrainedWorkSpecs.isEmpty()) {
Logger.get().debug(TAG, String.format("Starting tracking for [%s]",
TextUtils.join(",", constrainedWorkSpecIds)));
mConstrainedWorkSpecs.addAll(constrainedWorkSpecs);
mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
}
}
}
里面的代码看起来很多,其实很简单,分为两种情况,有约束条件就要判断做一些处理,无约束条件就直接startWork了。接着去看startWork里做了啥事,这里就回到了WorkManagerImpl中了。
public void startWork(
@NonNull String workSpecId,
@Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
mWorkTaskExecutor
.executeOnBackgroundThread(
new StartWorkRunnable(this, workSpecId, runtimeExtras));
}
又是一个StartWorkRunnable对象,接着去看他的run方法。
mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
回到创建的进程的startWork方法了。去看看里面做了啥事
public boolean startWork(
@NonNull String id,
@Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
workWrapper =
new WorkerWrapper.Builder(
mAppContext,
mConfiguration,
mWorkTaskExecutor,
this,
mWorkDatabase,
id)
.withSchedulers(mSchedulers)
.withRuntimeExtras(runtimeExtras)
.build();
//最终的执行
mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
}
最后丢给线程池去执行了。
总结
WorkManager还是挺好用的。