溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Android WorkManager的示例分析

發(fā)布時間:2021-06-26 11:37:37 來源:億速云 閱讀:291 作者:小新 欄目:移動開發(fā)

這篇文章主要介紹Android WorkManager的示例分析,文中介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們一定要看完!

一、原文翻譯

WorkManager API 可以很容易的指定可延遲的異步任務(wù)。允許你創(chuàng)建任務(wù),并把它交給WorkManager來立即運行或在適當(dāng)?shù)臅r間運行。WorkManager根據(jù)設(shè)備API的級別和應(yīng)用程序狀態(tài)等因素來選擇適當(dāng)?shù)姆绞竭\行任務(wù)。如果WorkManager在應(yīng)用程序運行時執(zhí)行你的任務(wù),它會在應(yīng)用程序進(jìn)程的新線程中執(zhí)行。如果應(yīng)用程序沒有運行,WorkManager會根據(jù)設(shè)備API級別和包含的依賴項選擇適當(dāng)?shù)姆绞桨才藕笈_任務(wù),可能會使用JobScheduler、Firebase JobDispatcher或AlarmManager。你不需要編寫設(shè)備邏輯來確定設(shè)備有哪些功能和選擇適當(dāng)?shù)腁PI;相反,你只要把它交給WorkManager讓它選擇最佳的方式。

Note:WorkManager適用于需要保證即使應(yīng)用程序退出系統(tǒng)也能運行任務(wù),比如上傳應(yīng)用數(shù)據(jù)到服務(wù)器。不適用于當(dāng)應(yīng)用程序退出后臺進(jìn)程能安全終止工作,這種情況推薦使用ThreadPools。

Android WorkManager的示例分析

功能:

基礎(chǔ)功能

  • 使用WorkManager創(chuàng)建運行在你選擇的環(huán)境下的單個任務(wù)或指定間隔的重復(fù)任務(wù)

  • WorkManager API使用幾個不同的類,有時,你需要繼承一些類。

  • Worker 指定需要執(zhí)行的任務(wù)。有一個抽象類Worker,你需要繼承并在此處工作。在后臺線程同步工作的類。WorkManager在運行時實例化Worker類,并在預(yù)先指定的線程調(diào)用doWork方法(見Configuration.getExecutor())。此方法同步處理你的工作,意味著一旦方法返回,Worker被視為已經(jīng)完成并被銷毀。如果你需要異步執(zhí)行或調(diào)用異步API,應(yīng)使用ListenableWorker。如果因為某種原因工作沒搶占,相同的Worker實例不會被重用。即每個Worker實例只會調(diào)用一次doWork()方法,如果需要重新運行工作單元,需要創(chuàng)建新的Worker。Worker最大10分鐘完成執(zhí)行并ListenableWorker.Result。如果過期,則會被發(fā)出信號停止。(Worker的doWork()方法是同步的,方法執(zhí)行完則結(jié)束,不會重復(fù)執(zhí)行,且默認(rèn)超時時間是10分鐘,超過則被停止。)

  • WorkRequest 代表一個獨立的任務(wù)。一個WorkRequest對象至少指定哪個Worker類應(yīng)該執(zhí)行該任務(wù)。但是,你還可以給WorkRequest添加詳細(xì)信息,比如任務(wù)運行時的環(huán)境。每個WorkRequest有一個自動生成的唯一ID,你可以使用ID來取消排隊的任務(wù)或獲取任務(wù)的狀態(tài)。WorkRequest是一個抽象類,你需要使用它一個子類,OneTimeWorkRequest或PeriodicWorkRequest。

    • WorkRequest.Builder 創(chuàng)建WorkRequest對象的幫助類,你需要使用子類OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder。

    • Constraints(約束) 指定任務(wù)執(zhí)行時的限制(如只有網(wǎng)絡(luò)連接時)。使用Constraints.Builder創(chuàng)建Constraints對象,并在創(chuàng)建WorkRequest對象前傳遞給WorkRequest.Builder。

  • WorkManager 排隊和管理WorkRequest。將WorkRequest對象傳遞給WorkManager來將任務(wù)添加到隊列。WorkManager 使用分散加載系統(tǒng)資源的方式安排任務(wù),同時遵守你指定的約束。

    • WorkManager使用一種底層作業(yè)調(diào)度服務(wù)基于下面的標(biāo)注

    • 使用JobScheduler API23+

    • 使用AlarmManager + BroadcastReceiver API14-22

  • WorkInfo 包含有關(guān)特定任務(wù)的信息。WorkManager為每個WorkRequest對象提供一個LiveData。LiveData持有WorkInfo對象,通過觀察LiveData,你可以確定任務(wù)的當(dāng)前狀態(tài),并在任務(wù)完成后獲取任何返回的值。

Android WorkManager的示例分析

二、源碼簡單分析

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

WorkerManager的具體實現(xiàn)類是WorkManagerImpl。

WorkManager不同的方法,會創(chuàng)建不同的***Runnable類來執(zhí)行。

下面是整體的包結(jié)構(gòu)

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());
  }

主要執(zhí)行在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的示例分析

最后會創(chuàng)建WorkerWrapper包裝類,來執(zhí)行我們定義的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,并調(diào)用了addListener方法,該回調(diào)方法會在調(diào)用set時執(zhí)行。

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

可見,在調(diào)用doWork()后,任務(wù)執(zhí)行完調(diào)用了set方法,此時會回調(diào)addListener方法。

addListener回調(diào)中主要用來判斷當(dāng)前任務(wù)的狀態(tài),所以如果任務(wù)被停止,此處展示捕獲的異常信息。

比如調(diào)用一個任務(wù)的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)

以上是“Android WorkManager的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI