软件编程
位置:首页>> 软件编程>> Android编程>> Android WorkManager浅谈

Android WorkManager浅谈

作者:XingJimmy  发布时间:2023-03-24 11:26:46 

标签:Android,WorkManager

一、原文翻译

WorkManager API 可以很容易的指定可延迟的异步任务。允许你创建任务,并把它交给WorkManager来立即运行或在适当的时间运行。WorkManager根据设备API的级别和应用程序状态等因素来选择适当的方式运行任务。如果WorkManager在应用程序运行时执行你的任务,它会在应用程序进程的新线程中执行。如果应用程序没有运行,WorkManager会根据设备API级别和包含的依赖项选择适当的方式安排后台任务,可能会使用JobScheduler、Firebase JobDispatcher或AlarmManager。你不需要编写设备逻辑来确定设备有哪些功能和选择适当的API;相反,你只要把它交给WorkManager让它选择最佳的方式。

Note:WorkManager适用于需要保证即使应用程序退出系统也能运行任务,比如上传应用数据到服务器。不适用于当应用程序退出后台进程能安全终止工作,这种情况推荐使用ThreadPools。

Android WorkManager浅谈

功能:

基础功能

  • 使用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 WorkManager浅谈

二、源码简单分析

android.arch.work:work-runtime-1.0.0-beta03

WorkerManager的具体实现类是WorkManagerImpl。

WorkManager不同的方法,会创建不同的***Runnable类来执行。

下面是整体的包结构

Android WorkManager浅谈

以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的子类

Android WorkManager浅谈

最后会创建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

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com