47

Job Scheduler is working as expected on Android Marshmallow and Lollipop devices, but it is not running and Nexus 5x (Android N Preview).

Code for scheduling the job

        ComponentName componentName = new ComponentName(MainActivity.this, TestJobService.class.getName());
        JobInfo.Builder builder;
        builder = new JobInfo.Builder(JOB_ID, componentName);
        builder.setPeriodic(5000);
        JobInfo jobInfo;
        jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        jobInfo = builder.build();
        int jobId = jobScheduler.schedule(jobInfo);

Service is defined in manifest as:

<service android:name=".TestJobService"
            android:permission="android.permission.BIND_JOB_SERVICE" />

Is any one having this issue on Android N (Preview)?

Adil Bhatty
  • 17,190
  • 34
  • 81
  • 118

4 Answers4

60

In Android Nougat the setPeriodic(long intervalMillis) method call makes use of setPeriodic (long intervalMillis, long flexMillis) to schedule periodic jobs.

As per the documentation:

JobInfo.Builder setPeriodic (long intervalMillis, long flexMillis)

Specify that this job should recur with the provided interval and flex. The job can execute at any time in a window of flex length at the end of the period.

intervalMillis long: Millisecond interval for which this job will repeat. A minimum value of getMinPeriodMillis() is enforced.

flexMillis long: Millisecond flex for this job. Flex is clamped to be at least getMinFlexMillis() or 5 percent of the period, whichever is higher.

Sample periodic job scheduled for 5 seconds:

private static final int JOB_ID = 1001;
private static final long REFRESH_INTERVAL  = 5 * 1000; // 5 seconds

JobInfo jobInfo = new JobInfo.Builder(JOB_ID, serviceName)
        .setPeriodic(REFRESH_INTERVAL)
        .setExtras(bundle).build();

The above code works well in Lollipop & Marshmallow but when you run in Nougat you will notice the following log:

W/JobInfo: Specified interval for 1001 is +5s0ms. Clamped to +15m0s0ms
W/JobInfo: Specified flex for 1001 is +5s0ms. Clamped to +5m0s0ms

Since we have set the periodic refresh interval to 5 seconds which is less than the thresholdgetMinPeriodMillis(). Android Nougat enforces the getMinPeriodMillis().

As a workaround, I'm using following code to schedule jobs at periodic intervals if job interval is less than 15minutes.

JobInfo jobInfo;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  jobInfo = new JobInfo.Builder(JOB_ID, serviceName)
      .setMinimumLatency(REFRESH_INTERVAL)
      .setExtras(bundle).build();
} else {
  jobInfo = new JobInfo.Builder(JOB_ID, serviceName)
      .setPeriodic(REFRESH_INTERVAL)
      .setExtras(bundle).build();
}

Sample JobService example:

public class SampleService extends JobService {
    @Override public boolean onStartJob(JobParameters params) {
        doSampleJob(params); 
        return true;
    }

    @Override public boolean onStopJob(JobParameters params) {
        return false;
    }

    public void doSampleJob(JobParameters params) {
        // Do some heavy operation
        ...... 
        // At the end inform job manager the status of the job.
        jobFinished(params, false);
    }
}
blizzard
  • 5,275
  • 2
  • 34
  • 48
  • 8
    but it seems to run only once with setMinimumLatency – Karthik Rk Oct 01 '16 at 14:18
  • 1
    @KarthikRk, the Job can't be periodic and have a minimumLatency, so if you can set minimum latency the it is a one shot job. – DataDino Oct 13 '16 at 01:25
  • 18
    This doesn't solve the problem... the job won't run periodically with this code. – forresthopkinsa Jun 29 '17 at 19:18
  • Even I have the same problem, I am trying out ways but not possible to get it wwork – Nandhan Thiravia Sep 15 '17 at 09:44
  • 1
    setMinimumLatency is for specifying that this job should be delayed by the provided amount of time. Because it doesn't make sense setting this property on a periodic job, doing so will throw an IllegalArgumentException when build() is called. – Badhrinath Canessane Oct 25 '17 at 18:22
  • same issue called only once. – Jay Dangar Sep 14 '18 at 07:11
  • Has anyone been able to set a periodic job on Nougat?? I have just run in into this same problem and I am not getting anywhere. Works fine in Oreo. – Chud37 Sep 21 '18 at 10:17
  • @JayDangar did you try the solution given below, I am not sure how this answer is still the accepted solution? – MRah Oct 03 '18 at 21:01
  • @MRah, I have tried this solution on my device, but it still runs the jon only once and not periodically. Also as JobScheduler wont work for API level < 21, I have to shift from JobSchedulers to https://github.com/evernote/android-job this library, which works perfectly for me. – Jay Dangar Oct 04 '18 at 05:39
  • onstop job not call after cancel all method, whats the solution ? – mehdi amirsardari Oct 14 '19 at 19:27
18

If someone is still trying to overcome the situation,

Here is a workaround for >= Android N (If you want to set the periodic job lower than 15 minutes)

Check that only setMinimumLatency is used. Also, If you are running a task that takes a long time, the next job will be scheduled at, Current JOB Finish time + PROVIDED_TIME_INTERVAL

.SetPeriodic(long millis) works well for API Level below Android N

@Override
public boolean onStartJob(final JobParameters jobParameters) {
    Log.d(TAG,"Running service now..");
    //Small or Long Running task with callback
    //Call Job Finished when your job is finished, in callback
    jobFinished(jobParameters, false );

    //Reschedule the Service before calling job finished
    if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
              scheduleRefresh();



    return true;
}

@Override
public boolean onStopJob(JobParameters jobParameters) {
    return false;
}

private void scheduleRefresh() {
  JobScheduler mJobScheduler = (JobScheduler)getApplicationContext()
                    .getSystemService(JOB_SCHEDULER_SERVICE);
  JobInfo.Builder mJobBuilder = 
  new JobInfo.Builder(YOUR_JOB_ID,
                    new ComponentName(getPackageName(), 
                    GetSessionService.class.getName()));

  /* For Android N and Upper Versions */
  if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
      mJobBuilder
                .setMinimumLatency(60*1000) //YOUR_TIME_INTERVAL
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
  }

UPDATE: If you are considering your repeating job to run while in Doze Mode and thinking about JobScheduler, FYI: JobSchedulers are not allowed to run in Doze mode.

I have not discussed about the Dozing because we were talking about JobScheduler. Thanks, @Elletlar, for pointing out that some may think that it will run even when the app is in doze mode which is not the case.

For doze mode, AlarmManager still gives the best solution. You can use setExactAndAllowWhileIdle() if you want to run your periodic job at exact time period or use setAndAllowWhileIdle() if you're flexible.

You can also user setAlarmClock() as device always comes out from doze mode for alarm clock and returns to doze mode again. Another way is to use FCM.

MRah
  • 940
  • 6
  • 15
  • 1
    Good workaround, my initial tests show that this is working better than FCM. – androidneil Oct 02 '18 at 21:33
  • @MRah I understand the schedule workaround, but why do you return true and not false from onStartJob? This leaves the job in "unfinished" state, doesn't it? – dor506 Oct 23 '18 at 06:43
  • @dor506, if you're doing a long-running task and return false then onStopJob is called and your task will not be done. So you return true and when your job is finished you call JobFinished(params,false) to release wakelock and stop the job. I edited the answer, hopefully, it will be more understandable. – MRah Oct 23 '18 at 18:16
  • @MRah In my case I don't need long running job, just a simple if statement. In this case I should call jobFinished -> schedule (if needed) -> return false. Right? – dor506 Oct 25 '18 at 06:04
  • help a lot. but I m very curious about that why google recommend JobScheduler if it doesn't work fine with setPeriodic . – Lonie Nov 15 '18 at 13:05
12

I have found the answer to the issue that we are facing with Nougat devices. Nougat devices are not able to schedule the job if the job needs to be re-scheduled lesser than 15 minutes.

I tried out by giving the Interval time as 15 minutes and the job started getting scheduled every 15 minutes.

Job Scheduler Code:

public static void scheduleJob(Context context) {
    ComponentName serviceComponent = new ComponentName(context, PAJobService.class);
    JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, serviceComponent);
    builder.setPeriodic(15 * 60 * 1000, 5 * 60 *1000);

    JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
    int ret = jobScheduler.schedule(builder.build());
    if (ret == JobScheduler.RESULT_SUCCESS) {
        Log.d(TAG, "Job scheduled successfully");
    } else {
        Log.d(TAG, "Job scheduling failed");
    }
}

Job Service:

public class PAJobService extends JobService {
    private static final String TAG = PRE_TAG + PAJobService.class.getSimpleName();
    private LocationManager mLocationManager;

    public boolean onStartJob(JobParameters params) {
        Log.d(TAG, "onStartJob");
        Toast.makeText(getApplicationContext(), "Job Started", Toast.LENGTH_SHORT).show();
        return false;
    }

    public boolean onStopJob(JobParameters params) {
        Log.d(TAG, "onStopJob");
        return false;
    }
}

In short if you would have increased the Interval Time to 15 minutes the code would have started working.

private static final long REFRESH_INTERVAL = 15 * 60 * 1000;

Nandhan Thiravia
  • 360
  • 2
  • 12
1

if you want to run the code periodically less than 15 minutes then you can use a tricky way. Set your jobFinished() like this

jobFinished(parameters, true);

it will reschedule the code with a retry strategy. Define a custom backoff criteria using

.setBackoffCriteria();

in the builder. Then it will run periodically

MarGin
  • 2,078
  • 1
  • 17
  • 28