Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 631
Adil Bhatty Avatar asked Jul 13 '16 06:07

Adil Bhatty


People also ask

How does JobScheduler work in PEGA?

If you configure the Job Scheduler to run on any node, the Pega Platform application chooses the node on which it processes the task. In the Schedule drop-down list, click the schedule for the Job Scheduler rule and provide additional details, such as a start time.

Does Android have a scheduler?

The Android 5.0 Lollipop (API 21) release introduces a job scheduler API via the JobScheduler class. This API allows to batch jobs when the device has more resources available. In general this API can be used to schedule everything that is not time critical for the user.

How does JobScheduler work in Android?

JobScheduler is introduced while the Android set limitation on background Execution. It means that the system will automatically destroy the background execution to save battery and memory. So if you want to perform some background job like some network operations no matter your app is running or not.


3 Answers

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);
    }
}
like image 117
blizzard Avatar answered Oct 21 '22 22:10

blizzard


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.

like image 18
MRah Avatar answered Oct 21 '22 20:10

MRah


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;

like image 12
Nandhan Thiravia Avatar answered Oct 21 '22 20:10

Nandhan Thiravia