使用WorkManager在特定时间安排工作

82
WorkManager是一个库,用于排队工作,保证在满足其约束条件后执行。
因此,在查看Constraints类之后,我没有找到任何添加时间约束的函数。例如,我想在早上8点开始执行工作(工作可以是OneTimeWorkRequestPeriodicWorkRequest中的任何一种)。如何添加约束以安排使用WorkManager执行此工作。

4
OneTimeWorkRequest.Builder#setInitialDelay 是一个方法,用于设置一次性工作请求的初始延迟时间。它需要两个参数:一个 long 类型的延迟时间和一个 java.util.concurrent.TimeUnit 枚举类型表示的时间单位。 - pskink
我认为这可能是解决问题的好方法。为此,我必须计算当前时间和我想要工作开始的时间之间的时间差。但是我能明确设置开始时间吗? - S Haque
计算 timeInFutureInSeconds - timeNowInSeconds 有什么问题吗? - pskink
@pskink,但问题是如何指定每天早上8点完成工作?初始延迟是指定的“初始”延迟。工作管理器如何知道我们需要运行它的下一个时间。 - Parth Anjaria
由于该任务是定期任务,WorkManager 会在第一次触发任务的同时触发该任务。 - S Haque
WorkManager 专门不支持精确时间使用情况。Google 已经在逐步减少对此类支持,如果你需要这样的功能,他们希望你使用推送通知来实现。他们希望任何从客户端预定的任务都是非精确的,以支持用户的电池寿命。 - davkutalek
8个回答

65

很遗憾,目前你无法在特定时间安排工作。如果您有时间关键的实现,则应使用AlarmManager设置闹钟,可以使用setAndAllowWhileIdle()setExactAndAllowWhileIdle()在Doze模式下触发。

您可以使用以下方式使用WorkManager安排一次性初始延迟或定期执行工作:

创建Worker类:

public class MyWorker extends Worker {
    @Override
    public Worker.WorkerResult doWork() {

        // Do the work here

        // Indicate success or failure with your return value:
        return WorkerResult.SUCCESS;

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}

然后按照以下方式安排OneTimeWorkRequest

OneTimeWorkRequest mywork=
        new OneTimeWorkRequest.Builder(MyWorker.class)
        .setInitialDelay(<duration>, <TimeUnit>)// Use this when you want to add initial delay or schedule initial work to `OneTimeWorkRequest` e.g. setInitialDelay(2, TimeUnit.HOURS)
        .build();
WorkManager.getInstance().enqueue(mywork);

您可以按照以下方式设置其他约束条件:

// Create a Constraints that defines when the task should run
Constraints myConstraints = new Constraints.Builder()
    .setRequiresDeviceIdle(true)
    .setRequiresCharging(true)
    // Many other constraints are available, see the
    // Constraints.Builder reference
     .build();

然后创建一个使用这些约束条件的OneTimeWorkRequest

OneTimeWorkRequest mywork=
                new OneTimeWorkRequest.Builder(MyWorker.class)
     .setConstraints(myConstraints)
     .build();
WorkManager.getInstance().enqueue(mywork);

可以按照以下方式创建PeriodicWorkRequest:

 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(MyWorker.class, 12, TimeUnit.HOURS)
                                   .build();
  WorkManager.getInstance().enqueue(periodicWork);

这将创建一个PeriodicWorkRequest,用于每12个小时定期运行一次。


6
问题在于它不适用于周期性工作。 - Gaket
2
@Gatek,这是整体的。它在Google IO中被提到过。如果您想要这样做,那么请安排OneTimeWorkRequest,在2小时后触发,并安排PeriodicWorkRequest在下午2点执行。 - Sagar
4
有两种setInitialDelay方法,一种需要传入Duration作为参数,只在API 26及以上版本可用(因为它来自Java 8),另一种需要传入duration和timeunit,在每个平台上都可用。 - NitroG42
46
这个答案是如何被接受的?问题声明我们必须在早上8点执行工作,但没有提到那个时间? - Parth Anjaria
3
这是因为使用 WorkManager 无法在特定时间安排工作。 - Sagar
显示剩余17条评论

21
从版本2.1.0-alpha02开始,PeriodicWorkRequests现在支持初始延迟。您可以使用PeriodicWorkRequest.Builder上的setInitialDelay方法来设置初始延迟。链接在这里 每天早上8:00的计划示例。这里我使用joda时间库进行时间操作。链接在这里
final int SELF_REMINDER_HOUR = 8;

    if (DateTime.now().getHourOfDay() < SELF_REMINDER_HOUR) {
        delay = new Duration(DateTime.now() , DateTime.now().withTimeAtStartOfDay().plusHours(SELF_REMINDER_HOUR)).getStandardMinutes();
    } else {
        delay = new Duration(DateTime.now() , DateTime.now().withTimeAtStartOfDay().plusDays(1).plusHours(SELF_REMINDER_HOUR)).getStandardMinutes();
    }


    PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(
        WorkerReminderPeriodic.class,
        24,
        TimeUnit.HOURS,
        PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS,
        TimeUnit.MILLISECONDS)
        .setInitialDelay(delay, TimeUnit.MINUTES)
        .addTag("send_reminder_periodic")
        .build();


    WorkManager.getInstance()
        .enqueueUniquePeriodicWork("send_reminder_periodic", ExistingPeriodicWorkPolicy.REPLACE, workRequest);

我相信未来执行时间的偏移量会非常大... 但是使用 REPLACE 看起来没问题。 - Vlad
对我来说,它将在24小时周期结束时仅第一次触发定期工作程序。有没有办法让它在创建后立即运行?(例如应在17:50运行,在17:40排队并在10分钟后首次运行) - Vadim Kotov
@Vlad,你能解释一下为什么你相信未来执行时间的偏移会非常大吗? - Soheil
@Anjal 为什么你要使用 ExistingPeriodicWorkPolicy.REPLACE 呢?! - Soheil
编辑:WorkManager提醒 09:00 或 16:00? - A. Sang
显示剩余2条评论

16

到目前为止,还无法使用PeriodicWorkRequest实现精确的时间。
一个丑陋的解决方法是使用 OneTimeWorkRequest,当其触发时,设置另一个具有新计算周期的 OneTimeWorkRequest,以此类推。


30
哇...那相当恶心 x.x - LeoColman
1
我不相信有理由从Evernote迁移到android-job。 - LeoColman
请查看我在此处的答案:https://dev59.com/LlQK5IYBdhLWcg3wZe9_#54394957 - Rafael Chagas
7
@Kerooker,实际上,WorkManager 的其中一位开发人员在此处建议 链接 - Vadim Kotov
@mhashim6,有一些例子吗? - Noor Hossain

7

我可能有点晚了,但是我还是想安排一个工作请求在指定的时间(可选短暂延迟)进行。您只需要从 TimePicker 中获取时间即可:

public static void scheduleWork(int hour, int minute) {
    Calendar calendar = Calendar.getInstance();
    long nowMillis = calendar.getTimeInMillis();

    calendar.set(Calendar.HOUR_OF_DAY,hour);
    calendar.set(Calendar.MINUTE,minute);
    calendar.set(Calendar.SECOND,0);
    calendar.set(Calendar.MILLISECOND,0);

    if (calendar.before(Calendar.getInstance())) {
        calendar.add(Calendar.DATE, 1);
    }

    long diff = calendar.getTimeInMillis() - nowMillis;

    WorkManager mWorkManager = WorkManager.getInstance();
    Constraints constraints = new Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build();
    mWorkManager.cancelAllWorkByTag(WORK_TAG);
    OneTimeWorkRequest mRequest = new OneTimeWorkRequest.Builder(NotificationWorker.class)
            .setConstraints(constraints)
            .setInitialDelay(diff,TimeUnit.MILLISECONDS)
            .addTag(WORK_TAG)
            .build();
    mWorkManager.enqueue(mRequest);

}

我尝试过,但经常不能按预期工作,因此最好使用AlarmManager。 - Nurseyit Tursunkulov
当然,如果需要精确的时间 https://developer.android.com/guide/background - Ignacio Garcia
1
现在的情况是Alarm Manager在后台运行时不会触发,而这个答案是完美的。 - Noor Hossain

2
你可以使用来自Evernote的AndroidJob。
class NotificationJob : DailyJob() {

override fun onRunDailyJob(params: Params): DailyJobResult {
       //your job       
       return DailyJobResult.SUCCESS

}

companion object {
    val TAG = "NotificationJob"
    fun scheduleJob() {
        //scheduled between 9-10 am

        DailyJob.schedule(JobRequest.Builder(TAG), 
            TimeUnit.HOURS.toMillis(9),TimeUnit.HOURS.toMillis(10 ))
     }
   }
 }

以及通知创建器

  class NotificationJobCreator : JobCreator {

        override fun create(tag: String): Job? {
           return when (tag) {
              NotificationJob.TAG ->
                NotificationJob()
              else ->
                null
           }
       }
  }

然后在你的Application类中启动

    JobManager.create(this).addJobCreator(NotificationJobCreator())

Gradle依赖项是:
    dependencies {
        implementation 'com.evernote:android-job:1.2.6'

        // or this with workmnager 
        implementation 'com.evernote:android-job:1.3.0-alpha08'
    }

2

所有答案现在都已经过时了,请升级到WorkManager 2.1.0-alpha02或更高版本。setInitialDelay()方法以前仅适用于OneTimeWorkRequest,但现在它们也支持PeriodicWorkRequest。

implementation "androidx.work:work-runtime:2.1.0-alpha02"

现在,PeriodicWorkRequests已经支持了初始延迟。您可以使用PeriodicWorkRequest.Builder上的setInitialDelay方法来设置初始延迟。

快速示例:

new PeriodicWorkRequest.Builder(MyWorker.class, MY_REPEATS, TimeUnit.HOURS)
        .setInitialDelay(THE_DELAY,TimeUnit.SECONDS);

1

0

我尝试过OneTimeWorkRequest,但它不太稳定(有时只能工作),所以我们不应该依赖它。AlarmManager是一个更好的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接