Android WorkManager 에 대한 간단 한 설명

16024 단어 AndroidWorkManager
번역
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 API 23+
  • 사용
  • AlarmManager+BroadcastReceiver API 14-22
  • 사용
  • WorkInfo 는 특정 임무 에 대한 정 보 를 포함한다.WorkManager 는 모든 WorkRequest 대상 에 게 LiveData 를 제공 합 니 다.LiveData 는 WorkInfo 대상 을 가지 고 있 습 니 다.LiveData 를 관찰 하면 작업 의 현재 상 태 를 확인 하고 작업 이 끝 난 후에 되 돌아 오 는 값 을 가 져 올 수 있 습 니 다.

  • 2.소스 코드 에 대한 간단 한 분석
    android.arch.work:work-runtime-1.0.0-beta03
    WorkerManager 의 구체 적 인 실현 클래스 는 WorkManager Impl 입 니 다.
    WorkManager 는 다른 방법 으로***Runnable 클래스 를 만들어 실행 합 니 다.
    다음은 전체적인 가방 구조 입 니 다.

    Enqueue Runnable 을 예 로 들 면...
    
    @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 의 하위 클래스 를 살 펴 보 겠 습 니 다.

    마지막 으로 Worker Wrapper 포장 류 를 만 들 고 우리 가 정의 하 는 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();
        }
      }
    
    
    안 드 로 이 드 x.work.impl.utils.future.Settable Future 를 사용 하고 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)
    
    이상 은 제 간단 한 분석 입 니 다.아직 말 하지 않 은 것 이 많 습 니 다.나중에 시간 이 있 으 면 계속 할 것 입 니 다.
    옳지 않 은 비판 지적 을 환영 합 니 다.여러분 의 학습 에 도움 이 되 기 를 바 랍 니 다.여러분 들 도 저 희 를 많이 응원 해 주시 기 바 랍 니 다.

    좋은 웹페이지 즐겨찾기