androidandroid-job

Have a job run at certain time of day, everyday with Evernote's android-job


Is their a pattern for having Evernote's android-job run a job at between 1AM and 2AM everyday?

I was thinking it might make sense to do something like having in my Application.onCreate and at the end of my Job.onRunJob

// use the current time to see how long it will be until 1AM
long timeUntil1Am = getTimeUntil1Am(currentUnixTimeStamp);

new JobRequest.Builder(DemoSyncJob.TAG)
            .setExecutionWindow(timeUntil1Am, timeUntil1Am + 3600_000L)
            .setBackoffCriteria(5_000L, JobRequest.BackoffPolicy.EXPONENTIAL)
            .setRequiresCharging(true)
            .setRequiresDeviceIdle(false)
            .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
            .setRequirementsEnforced(true)
            .setPersisted(true)
            .setUpdateCurrent(true)
            .build()
            .schedule();

which would run the job the first time at ~1AM from the application on create and then use the onRunJob to daisy chain the next run time.

I think this would work, but I am concerned that the job would be scheduled multiple times because a new job is created every time the Application is built. If I knew the job was already scheduled I could avoid this, but not sure if that is possible.

So my question is using this daisy chain a reasonable pattern and how do I avoid running the job more than once per day?


Solution

  • I used something similar to the pattern in the FAQ mentioned by @CommonsWare

    To make this easier i created a wrapper class to get a daily execution window

    Class to get start and endtime relative to current time

    class DailyExecutionWindow {
        final long startMs;
        final long endMs;
    
        /**
         * Holds the start end time in ms for a job.
         * Will wrap around to next day if currentHour < targetHour.
         * If the current time is exactly now it will be forced to 60 seconds in the future.
         *
         * @param currentHour - current currentHour
         * @param currentMinute - current currentMinute
         * @param targetHour - currentHour we want to start
         * @param targetMinute - currentMinute we want to start
         * @param windowLengthInMinutes - number of minutes for the execution window
         */
        DailyExecutionWindow(int currentHour, int currentMinute, long targetHour, long targetMinute, long windowLengthInMinutes) {
            long hourOffset;
            long minuteOffset;
    
            if (targetHour == currentHour && targetMinute < currentMinute) {
                hourOffset = TimeUnit.HOURS.toMillis(23);
            } else if (targetHour - currentHour == 1) { // if we are less then an hour ahead, but into the next hour
                // move forward to 0 minute of next hour
                hourOffset = TimeUnit.MINUTES.toMillis(60 - currentMinute);
                currentMinute = 0;
            } else if (targetHour >= currentHour) {
                hourOffset = TimeUnit.HOURS.toMillis(targetHour - currentHour);
            } else {
                hourOffset = TimeUnit.HOURS.toMillis((24 + targetHour) - currentHour);
            }
    
            if (targetMinute >= currentMinute) {
                minuteOffset = TimeUnit.MINUTES.toMillis(targetMinute - currentMinute);
            } else {
                minuteOffset = TimeUnit.MINUTES.toMillis((60 + targetMinute) - currentMinute);
            }
    
            this.startMs = Math.max(hourOffset + minuteOffset, 60000);
            this.endMs = this.startMs + TimeUnit.MINUTES.toMillis(windowLengthInMinutes);
    
        }
    }
    

    My chaining Job implementation

    public class UpdateFeedsJob extends Job {
    
        public static final String TAG = UpdateFeedsJob.class.getName();
        private static final long TARGET_HOUR = 2L;
        private static final long TARGET_MINUTE = 15;
        private static final long WINDOW_LENGTH = 60;
        private static final int WAKE_LOCK_AWAIT_TIME_SECONDS = 60;
    
        // called in <MyApplication extends Application>.onCreate()
        public static void schedule() {
            schedule(true);
        }
    
        private static void schedule(boolean updateCurrent) {
            Calendar calendar = Calendar.getInstance();
            int hour = calendar.get(Calendar.HOUR_OF_DAY);
            int minute = calendar.get(Calendar.MINUTE);
    
            DailyExecutionWindow executionWindow =
                    new DailyExecutionWindow(hour, minute, TARGET_HOUR, TARGET_MINUTE, WINDOW_LENGTH);
    
            new JobRequest.Builder(UpdateFeedsJob.TAG)
                    .setExecutionWindow(executionWindow.startMs, executionWindow.endMs)
                    .setPersisted(true)
                    .setUpdateCurrent(updateCurrent)
                    .build()
                    .schedule();
        }
    
        @NonNull
        @Override
        protected Result onRunJob(Params params) {
            try {
                // ... do work
                return Result.SUCCESS;
            } finally {
                schedule(false);
            }
            return Result.FAILURE;
        }
    }
    

    You can see a working example in my Podcast Player Application on github