JetPack源码分析之WorkManager原理(三)

在继续梳理代码之前,需要先想清楚一个问题,那就是为什么 WorkManager 会使用 Room 来管理任务状态,运行时状态在使用过程中的各种问题

先说一下为什么运行时状态不可以,如果添加的是一个重复的任务,那么就代表着后续事件的执行逻辑脱离了控制,他的执行时机可能是下次app启动,也可能是 JobService 拉起的服务,由于App的状态是不稳定的,就导致运行时状态的不可靠性.

下面继续来分析 WorkContinuationImpl 的代码,在创建成功后会调用他的 enqueue 方法来驱动事件

less 复制代码
@Override
public @NonNull Operation enqueue() {
    // Only enqueue if not already enqueued.
    if (!mEnqueued) {
        // The runnable walks the hierarchy of the continuations
        // and marks them enqueued using the markEnqueued() method, parent first.
        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;
}

这个 enqueue 方法就是启动了一个Runnable ,并且使用线程池来运行 ,继续查看 EnqueueRunnable 的 run 方法

scss 复制代码
@Override
public void run() {
   try {
       if (mWorkContinuation.hasCycles()) {
           throw new IllegalStateException(
                   String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
       }
       boolean needsScheduling = addToDatabase();
       if (needsScheduling) {
           // Enable RescheduleReceiver, only when there are Worker's that need scheduling.
           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));
   }
}

可以看到先更新数据库,然后在分发任务 看一下任务的分发过程

scss 复制代码
public void scheduleWorkInBackground() {
    WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
    Schedulers.schedule(
            workManager.getConfiguration(),
            workManager.getWorkDatabase(),
            workManager.getSchedulers());
}

任务的实际分发是在 Schedulers 中完成的

less 复制代码
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> eligibleWorkSpecsForLimitedSlots;
    List<WorkSpec> allEligibleWorkSpecs;

    workDatabase.beginTransaction();
    try {
        // Enqueued workSpecs when scheduling limits are applicable.
        eligibleWorkSpecsForLimitedSlots = workSpecDao.getEligibleWorkForScheduling(
                configuration.getMaxSchedulerLimit());

        // Enqueued workSpecs when scheduling limits are NOT applicable.
        allEligibleWorkSpecs = workSpecDao.getAllEligibleWorkSpecsForScheduling(
                MAX_GREEDY_SCHEDULER_LIMIT);

        if (eligibleWorkSpecsForLimitedSlots != null
                && eligibleWorkSpecsForLimitedSlots.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 : eligibleWorkSpecsForLimitedSlots) {
                workSpecDao.markWorkSpecScheduled(workSpec.id, now);
            }
        }
        workDatabase.setTransactionSuccessful();
    } finally {
        workDatabase.endTransaction();
    }

    if (eligibleWorkSpecsForLimitedSlots != null
            && eligibleWorkSpecsForLimitedSlots.size() > 0) {

        WorkSpec[] eligibleWorkSpecsArray =
                new WorkSpec[eligibleWorkSpecsForLimitedSlots.size()];
        eligibleWorkSpecsArray =
                eligibleWorkSpecsForLimitedSlots.toArray(eligibleWorkSpecsArray);

        // Delegate to the underlying schedulers.
        for (Scheduler scheduler : schedulers) {
            if (scheduler.hasLimitedSchedulingSlots()) {
                scheduler.schedule(eligibleWorkSpecsArray);
            }
        }
    }

    if (allEligibleWorkSpecs != null && allEligibleWorkSpecs.size() > 0) {
        WorkSpec[] enqueuedWorkSpecsArray = new WorkSpec[allEligibleWorkSpecs.size()];
        enqueuedWorkSpecsArray = allEligibleWorkSpecs.toArray(enqueuedWorkSpecsArray);
        // Delegate to the underlying schedulers.
        for (Scheduler scheduler : schedulers) {
            if (!scheduler.hasLimitedSchedulingSlots()) {
                scheduler.schedule(enqueuedWorkSpecsArray);
            }
        }
    }
}

在分发过程中还是先更新数据库,再根据实际情况选择不同的调度器来来调度任务,如果是有约束条件的的就使用SystemJobScheduler 等待系统调度,如果没有约束条件就使用 GreedyScheduler 来调度,这里我们跟踪一下 GreedyScheduler 的调度过程

scss 复制代码
@Override
public void schedule(@NonNull WorkSpec... workSpecs) {
   if (mInDefaultProcess == null) {
       checkDefaultProcess();
   }

   if (!mInDefaultProcess) {
       Logger.get().info(TAG, "Ignoring schedule request in a secondary 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.
   Set<WorkSpec> constrainedWorkSpecs = new HashSet<>();
   Set<String> constrainedWorkSpecIds = new HashSet<>();

   for (WorkSpec workSpec : workSpecs) {
       long nextRunTime = workSpec.calculateNextRunTime();
       long now = System.currentTimeMillis();
       if (workSpec.state == WorkInfo.State.ENQUEUED) {
           if (now < nextRunTime) {
               // Future work
               if (mDelayedWorkTracker != null) {
                   mDelayedWorkTracker.schedule(workSpec);
               }
           } else 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);
       }
   }
}

从代码上可以看到,是根据任务的状态来判断的,当前的任务状态是 ENQUEUED 已经入队,和他的延迟时间来处理的,没有延迟时间就直接调用了 mWorkManagerImpl.startWork(workSpec.id); 这个方法

less 复制代码
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void startWork(
       @NonNull String workSpecId,
       @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
   mWorkTaskExecutor
           .executeOnBackgroundThread(
                   new StartWorkRunnable(this, workSpecId, runtimeExtras));
}

这里又是使用了 StartWorkRunnable 将任务包装了一下,并使用 线程池来执行任务

typescript 复制代码
public class StartWorkRunnable implements Runnable {

   private WorkManagerImpl mWorkManagerImpl;
   private String mWorkSpecId;
   private WorkerParameters.RuntimeExtras mRuntimeExtras;

   public StartWorkRunnable(
           WorkManagerImpl workManagerImpl,
           String workSpecId,
           WorkerParameters.RuntimeExtras runtimeExtras) {
       mWorkManagerImpl = workManagerImpl;
       mWorkSpecId = workSpecId;
       mRuntimeExtras = runtimeExtras;
   }

   @Override
   public void run() {
       mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
   }
}

StartWorkRunnable 的方法也是非常的简单,使用 Processor 来启动任务

less 复制代码
public boolean startWork(
       @NonNull String id,
       @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {

   WorkerWrapper workWrapper;
   synchronized (mLock) {
       // Work may get triggered multiple times if they have passing constraints
       // and new work with those constraints are added.
       if (isEnqueued(id)) {
           Logger.get().debug(
                   TAG,
                   String.format("Work %s is already enqueued for processing", id));
           return false;
       }

       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);
   }
   mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
   Logger.get().debug(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id));
   return true;
}

Processor 这里是先将任务通过id构建出来,在将任务包装一下,再通过 线程池来执行 WorkerWrapper 这个Runnable,继续看一下 WorkerWrapper 的run方法

scss 复制代码
@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);
           mWorkDatabase.setTransactionSuccessful();
           return;
       }

       // Do a quick check to make sure we don't need to bail out in case this work is already
       // running, finished, or is blocked.
       if (mWorkSpec.state != ENQUEUED) {
           resolveIncorrectStatus();
           mWorkDatabase.setTransactionSuccessful();
           Logger.get().debug(TAG,
                   String.format("%s is not in ENQUEUED state. Nothing more to do.",
                           mWorkSpec.workerClassName));
           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();
           // Allow first run of a PeriodicWorkRequest
           // to go through. This is because when periodStartTime=0;
           // calculateNextRunTime() always > now.
           // For more information refer to b/124274584
           boolean isFirstRun = mWorkSpec.periodStartTime == 0;
           if (!isFirstRun && now < mWorkSpec.calculateNextRunTime()) {
               Logger.get().debug(TAG,
                       String.format(
                               "Delaying execution for %s because it is being executed "
                                       + "before schedule.",
                               mWorkSpec.workerClassName));
               // For AlarmManager implementation we need to reschedule this kind  of Work.
               // This is not a problem for JobScheduler because we will only reschedule
               // work if JobScheduler is unaware of a jobId.
               resolve(true);
               mWorkDatabase.setTransactionSuccessful();
               return;
           }
       }

       // Needed for nested transactions, such as when we're in a dependent work request when
       // using a SynchronousExecutor.
       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 {
       InputMergerFactory inputMergerFactory = mConfiguration.getInputMergerFactory();
       String inputMergerClassName = mWorkSpec.inputMergerClassName;
       InputMerger inputMerger =
               inputMergerFactory.createInputMergerWithDefaultFallback(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);
   }

   final WorkerParameters params = new WorkerParameters(
           UUID.fromString(mWorkSpecId),
           input,
           mTags,
           mRuntimeExtras,
           mWorkSpec.runAttemptCount,
           mConfiguration.getExecutor(),
           mWorkTaskExecutor,
           mConfiguration.getWorkerFactory(),
           new WorkProgressUpdater(mWorkDatabase, mWorkTaskExecutor),
           new WorkForegroundUpdater(mWorkDatabase, mForegroundProcessor, mWorkTaskExecutor));

   // 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();
       final WorkForegroundRunnable foregroundRunnable =
               new WorkForegroundRunnable(
                       mAppContext,
                       mWorkSpec,
                       mWorker,
                       params.getForegroundUpdater(),
                       mWorkTaskExecutor
               );
       mWorkTaskExecutor.getMainThreadExecutor().execute(foregroundRunnable);

       final ListenableFuture<Void> runExpedited = foregroundRunnable.getFuture();
       runExpedited.addListener(new Runnable() {
           @Override
           public void run() {
               try {
                   runExpedited.get();
                   Logger.get().debug(TAG,
                           String.format("Starting work for %s", mWorkSpec.workerClassName));
                   // Call mWorker.startWork() on the main thread.
                   mInnerFuture = mWorker.startWork();
                   future.setFuture(mInnerFuture);
               } catch (Throwable e) {
                   future.setException(e);
               }
           }
       }, mWorkTaskExecutor.getMainThreadExecutor());

       // 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 {
                       Logger.get().debug(TAG, String.format("%s returned a %s result.",
                               mWorkSpec.workerClassName, result));
                       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();
   }
}

在run 方法内创建了一些变量后,就调用了他的 runWorker 方法 ,在这里还是先更新了数据库, 并且在 runExpedited 添加了一个 Listener ,在这个 Listener 中执行了 mWorker.startWork()

java 复制代码
public abstract class Worker extends ListenableWorker {

    // Package-private to avoid synthetic accessor.
    SettableFuture<Result> mFuture;

    @Keep
    @SuppressLint("BanKeepAnnotation")
    public Worker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    /**
     * Override this method to do your actual background processing.  This method is called on a
     * background thread - you are required to <b>synchronously</b> do your work and return the
     * {@link androidx.work.ListenableWorker.Result} from this method.  Once you return from this
     * method, the Worker is considered to have finished what its doing and will be destroyed.  If
     * you need to do your work asynchronously on a thread of your own choice, see
     * {@link ListenableWorker}.
     * <p>
     * A Worker has a well defined
     * <a href="https://d.android.com/reference/android/app/job/JobScheduler">execution window</a>
     * to finish its execution and return a {@link androidx.work.ListenableWorker.Result}.  After
     * this time has expired, the Worker will be signalled to stop.
     *
     * @return The {@link androidx.work.ListenableWorker.Result} of the computation; note that
     *         dependent work will not execute if you use
     *         {@link androidx.work.ListenableWorker.Result#failure()} or
     *         {@link androidx.work.ListenableWorker.Result#failure(Data)}
     */
    @WorkerThread
    public abstract @NonNull Result doWork();

    @Override
    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;
    }
}

到了这里就到了最后一步 Worker 的 startWork 方法, 在 startWork 方法里面调用了 抽象方法 doWork 方法,整个流程结束,

相关推荐
帅次6 小时前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
IAM四十二2 天前
Jetpack Compose State 你用对了吗?
android·android jetpack·composer
Wgllss4 天前
那些大厂架构师是怎样封装网络请求的?
android·架构·android jetpack
x02421 天前
Android Room(SQLite) too many SQL variables异常
sqlite·安卓·android jetpack·1024程序员节
alexhilton23 天前
深入理解观察者模式
android·kotlin·android jetpack
Wgllss24 天前
花式高阶:插件化之Dex文件的高阶用法,极少人知道的秘密
android·性能优化·android jetpack
上官阳阳1 个月前
使用Compose创造有趣的动画:使用Compose共享元素
android·android jetpack
沐言人生1 个月前
Android10 Framework—Init进程-15.属性变化控制Service
android·android studio·android jetpack
IAM四十二1 个月前
Android Jetpack Core
android·android studio·android jetpack
王能1 个月前
Kotlin真·全平台——Kotlin Compose Multiplatform Mobile(kotlin跨平台方案、KMP、KMM)
android·ios·kotlin·web·android jetpack·kmp·kmm