Job Scheduler not running on Android N
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)?
Solution 1:
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);
}
}
Solution 2:
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.
Solution 3:
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;
Solution 4:
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