Schedule a work on a specific time with WorkManage

2020-01-23 11:29发布

问题:

WorkManager is a library used to enqueue work that is guaranteed to execute after its constraints are met.

Hence, After going though the Constraints class I haven't found any function to add time constraint on the work. For like example, I want to start a work to perform at 8:00am (The work can be any of two types OneTimeWorkRequest or PeriodicWorkRequest) in the morning. How can I add constraint to schedule this work with WorkManager.

回答1:

Unfortunately, you cannot schedule a work at specific time as of now. If you have time critical implementation then you should use AlarmManager to set alarm that can fire while in Doze to by using setAndAllowWhileIdle() or setExactAndAllowWhileIdle().

You can schedule a work, with onetime initial delay or execute it periodically, using the WorkManager as follows:

Create Worker class:

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.)
    }
}

Then schedule OneTimeWorkRequest as follows:

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

You can setup additional constraints as follows:

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

Then create a OneTimeWorkRequest that uses those constraints

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

PeriodicWorkRequest can be created as follows:

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

This creates a PeriodicWorkRequest to run periodically once every 12 hours.



回答2:

PeriodicWorkRequests now support initial delays from the version 2.1.0-alpha02. You can use the setInitialDelay method on PeriodicWorkRequest.Builder to set an initial delay. Link Here

Example of schedule at every day at 8:00 am. here I'm using joda time library for time operations.

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


回答3:

So far it's not possible to achieve exact times using PeriodicWorkRequest.
An ugly work-around would be using a OneTimeWorkRequest and when it fires, set another OneTimeWorkRequest with a new calculated period, and so on.



回答4:

You can use AndroidJob from evernote

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

and a notification creator

  class NotificationJobCreator : JobCreator {

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

then initiate in your Application class

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

The gradle dependency is

    dependencies {
        implementation 'com.evernote:android-job:1.2.6'

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


回答5:

I might be somewhat late but anyway I did this in order to schedule a WorkRequest at a given time (with an optional short delay). You just need to get the time from a TimePicker of whatever:

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

    if(calendar.get(Calendar.HOUR_OF_DAY) > hour ||
            (calendar.get(Calendar.HOUR_OF_DAY) == hour && calendar.get(Calendar.MINUTE)+1 >= minute)) {
        calendar.add(Calendar.DAY_OF_MONTH, 1);
    }

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

    calendar.set(Calendar.SECOND,0);
    calendar.set(Calendar.MILLISECOND,0);
    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);

}


回答6:

If you want to set an initial delay to a PeriodicWorkRequest, I presented the solution here:

Set initial delay to a Periodic Work Manager in Android



回答7:

All answers are now obsolete, upgrade to WorkManager 2.1.0-alpha02 (or beyond) setInitialDelay() method used to work only for OneTimeWorkRequest, but now they also support PeriodicWorkRequest.

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

PeriodicWorkRequests now support initial delays. You can use the setInitialDelay method on PeriodicWorkRequest.Builder to set an initial delay

quick example:

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


回答8:

I tried OneTimeWorkRequest but it is flaky(work only sometimes) so we should not rely on it. AlarmManager is a better option.