欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android WorkManager浅谈

程序员文章站 2022-04-29 10:33:49
一、原文翻译 workmanager api 可以很容易的指定可延迟的异步任务。允许你创建任务,并把它交给workmanager来立即运行或在适当的时间运行。work...

一、原文翻译

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)

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

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