服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android WorkManager浅谈

Android WorkManager浅谈

2022-10-28 13:55XingJimmy Android

这篇文章主要介绍了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为例

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@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类中

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
   * 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类。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@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时执行。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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类

?
1
2
3
4
5
6
7
8
9
10
11
12
@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
2
3
4
5
6
7
8
9
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

延伸 · 阅读

精彩推荐
  • Android浅谈关于android软键盘弹出问题

    浅谈关于android软键盘弹出问题

    本篇文章主要介绍了浅谈关于android软键盘弹出问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    JJ_code12172022-02-20
  • AndroidAndroid数据类型之间相互转换系统介绍

    Android数据类型之间相互转换系统介绍

    一些初学Android的朋友可能会遇到JAVA的数据类型之间转换的苦恼;本文将为有这类需求的朋友解决此类问题...

    Android教程网2752020-12-18
  • Androidandroid 使用浏览器打开指定页面的实现方法

    android 使用浏览器打开指定页面的实现方法

    这篇文章主要介绍了android 使用浏览器打开指定页面的实现方法,本文通过实例文字说明的形式给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋...

    bzlj29120095967392022-03-02
  • Android详解Android过滤emoji表情正则表达式

    详解Android过滤emoji表情正则表达式

    这篇文章主要介绍了Android过滤emoji表情正则表达式,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下...

    ganchuanpu4772022-03-06
  • AndroidAndroid下Activity全屏显示实现方法

    Android下Activity全屏显示实现方法

    这篇文章主要介绍了Android下Activity全屏显示实现方法,以两种不同的方法来实现这一技巧,非常具有实用性,需要的朋友可以参考下...

    Android开发网7512021-03-10
  • AndroidAndroid 暂停和恢复Activity

    Android 暂停和恢复Activity

    在正常的应用程序使用,前台activity有时会被其他可视化组件遮挡,从而 造成activity的暂停。例如,当一个半透明的activity打开时(如在一个风格对话框),...

    jerrylsxu11412021-06-25
  • Androidandroid应用开发之spinner控件的简单使用

    android应用开发之spinner控件的简单使用

    Android的控件有很多种,其中就有一个Spinner的控件,这个控件其实就是一个下拉显示列表。本文通过脚本之家平台给大家介绍android应用开发之spinner控件的简...

    千个太阳在手中5182021-04-08
  • AndroidFlutter进阶质感设计之标签栏

    Flutter进阶质感设计之标签栏

    这篇文章主要为大家详细介绍了Flutter进阶质感设计之标签栏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    何小有5072022-07-24