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 方法,整个流程结束,

相关推荐
开发者阿伟21 小时前
Android Jetpack LiveData源码解析
android·android jetpack
开发者阿伟4 天前
Android Jetpack DataBinding源码解析与实践
android·android jetpack
alexhilton10 天前
Android技巧:学习使用GridLayout
android·kotlin·android jetpack
Wgllss17 天前
轻松搞定Android蓝牙打印机,双屏异显及副屏分辨率适配解决办法
android·架构·android jetpack
alexhilton24 天前
群星闪耀的大前端开发
android·kotlin·android jetpack
一航jason1 个月前
Android Jetpack Compose 现有Java老项目集成使用compose开发
android·java·android jetpack
帅次1 个月前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
IAM四十二2 个月前
Jetpack Compose State 你用对了吗?
android·android jetpack·composer
Wgllss2 个月前
那些大厂架构师是怎样封装网络请求的?
android·架构·android jetpack
x0242 个月前
Android Room(SQLite) too many SQL variables异常
sqlite·安卓·android jetpack·1024程序员节