一、原文翻译

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

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

功能:

基础功能

  • 使用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.arch.work:work-runtime-1.0.0-beta03

WorkerManager的具体实现类是WorkManagerImpl。

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

下面是整体的包结构

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

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

以上就是我的简单分析,还有好多没有说到,后面有时间会继续。

有不对的欢迎批评指正。希望对大家的学习有所帮助,也希望大家多多支持Devmax。

Android WorkManager浅谈的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

  10. Android – 调用GONE然后VISIBLE使视图显示在错误的位置

    我有两个视图,A和B,视图A在视图B上方.当我以编程方式将视图A设置为GONE时,它将消失,并且它正下方的视图将转到视图A的位置.但是,当我再次将相同的视图设置为VISIBLE时,它会在视图B上显示.我不希望这样.我希望视图B回到原来的位置,这是我认为会发生的事情.我怎样才能做到这一点?编辑–代码}这里是XML:解决方法您可以尝试将两个视图放在RelativeLayout中并相对于彼此设置它们的位置.

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部