77

I'm using JobScheduler to schedule jobs. Mainly I'm using it for the .setRequiredNetworkType() method, which allows you to specify that you only want the job to be scheduled when a network connection (or more specifically an unmetered connection) is established.

I'm using the following pretty straightforward code to schedule my jobs:

PersistableBundle extras = new PersistableBundle();
extras.putInt("anExtraInt", someInt);
int networkConstraint = useUnmetered ? JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY;

ComponentName componentName = new ComponentName(context, MyJobService.class);
JobInfo jobInfo = new JobInfo.Builder(jobId, componentName)
        .setRequiredNetworkType(networkConstraint)
        .setExtras(extras)
        .build();

JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(jobInfo);

So there is just one constraint placed on the scheduling: a network connection (which may be 'any' or 'unmetered').

The short version of the question

How do I specify a maximum delay from all constraints being met, and actually running the job, e.g. "run the job within 2 seconds of there being a network connection"?

Longer version (with ramblings)

The Problem

What I'm finding is that on some devices, if a job is scheduled during a period in which the network constraint is already satisfied, the job will run immediately (or quickly enough to be perceived as so by the user).

But on other devices, even if a suitable network connection is already available (so that the job could run immediately), there is a significant delay before it actually runs. So if this is in response to a user action, the impression is that nothing has happened and that the app is broken.

Now, I'm well aware that this is probably the intention with JobScheduler... that it's up to the system to schedule the job to best fit in with other demands, and that there is no guarantee that the job will run immediately when all constraints are satisfied.

But it would be nice to be able to have some control over it, where required. So for jobs that happen on a schedule, without user involvement, giving the system complete control over precise timing is fine and good.

But where the job is in response to a user action, I want the job to run without delay... assuming the network connection is there. (If there is no connection, a message can be displayed that the action will happen when a network connection is restored, and the JobScheduler then takes care of ensuring the job runs when the network is restored.)

setOverrideDeadline() is not a solution?

I can see that JobInfo.Builder does have a setOverrideDeadline() method, which is almost what I want. But that specifies the maximum delay from when the job is scheduled (i.e. run the job in 10 seconds' time even if all constraints are not met), and not from when all constraints have been satisfied (i.e. run the job within 10 seconds of all constraints being satisfied).

EDIT: there seems to be an annoying bug that can result in the job being run twice when using setOverrideDeadline(): see here and here.

What about Firebase JobDispatcher?

I see that Firebase JobDispatcher has a Trigger.NOW trigger ("means that the Job should be run as soon as its runtime constraints are satisfied"). Perhaps that's the way to go if JobSchedulerdoesn't support this natively? I've been put off by Firebase JobDispatcher because it seems like it's using a sledgehammer to crack a nut... and it appears that Firebase is all about cloud messaging etc, which is very far removed from local task scheduling (which should be entirely local concern). And it seems to require Google Play services, which again seems completely unnecessary for local task scheduling. Furthermore, if immediate triggering is possible with Firebase, and Firebase just uses JobScheduler for Android L+, then it must surely be possible to do this directly with JobScheduler without relying on Firebase?

EDIT: I've now tried this, and even Trigger.NOW doesn't guarantee an immediate response... in fact, I am finding that there is a delay of almost exactly 30 seconds on my device, which is odd.

Failing that...

At present, the only way I can see to ensure immediate execution (if constraints are met) is to not use JobScheduler.

Or maybe do the initial constraints check manually, and run the job with a setOverrideDeadline() of 0 if all constraints are met, otherwise run it without setOverrideDeadline().

It would seem far preferable just to have the ability to control the timing of JobScheduler itself, a bit as you can with the setWindow() method of AlarmManager.

15
  • @OP could you please organize your post into smaller sub categories about what you are trying to say with respect to the problem you are trying to fix?
    – JoxTraex
    Commented Sep 8, 2016 at 8:00
  • @JoxTraex I had already attempted that (short version v long version) but I've added more substructure now. Nobody need read beyond the short version, but the long version is there for more background.
    – drmrbrewer
    Commented Sep 8, 2016 at 8:42
  • 1
    As you suggested, I suspect that this lack of API is by design. You're certainly welcome to ask for it as a feature enhancement, though any such enhancement would not show up until Android O at best (presumably), meaning it'll be 2022 before such a feature becomes commonplace. "maybe do the initial constraints check manually, and run the job with a setOverrideDeadline() of 0 if all constraints are met, otherwise run it without setOverrideDeadline()" -- or, just do the work if the constraints are met, and skip the job. No sense in using JobScheduler for immediate work. Commented Sep 10, 2016 at 20:17
  • So @CommonsWare if I have a JobService set up to carry out the scheduled job, is there any way of running that directly, not via a JobScheduler, if I want it just to run now? I hate code duplication, and to create a separate Service for this (even with shared functions) seems inelegant.... much neater just to start the JobService directly?
    – drmrbrewer
    Commented Sep 10, 2016 at 21:08
  • "if I want it just to run now?" -- AFAIK, you can call startService() on your own JobService. Refactor the real work to be done into a separate class (for your background thread), one that can be created and used from onStartJob() or from startService(). It doesn't look like you can create a JobParameters yourself, so you cannot have startService() call onStartJob(), unless you passed null for the JobParameters. Commented Sep 10, 2016 at 21:12

2 Answers 2

1

A job scheduler is to schedule jobs: triggering periodically, with delay or with constraints to other jobs. If you want to fire a job instantly, it doesn't need to bee scheduled, just launch it.

ConnectivityManager cm =
            (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = cm.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnectedOrConnecting()) {

        // If there is connectivity, Launch the job directly here

    } else {

        PersistableBundle extras = new PersistableBundle();
        extras.putInt("anExtraInt", someInt);
        int networkConstraint = useUnmetered ?       
        JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY;

        ComponentName componentName = new ComponentName(context,MyJobService.class);
        JobInfo jobInfo = new JobInfo.Builder(jobId, componentName)
                .setRequiredNetworkType(networkConstraint)
                .setExtras(extras)
                .build();

        JobScheduler jobScheduler = (JobScheduler)      context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
        jobScheduler.schedule(jobInfo);
    }
1
  • This is the way. I would recommend using WorkManager instead of JobScheduler, specifically OneTimeWorkRequest.
    – Bip901
    Commented Jul 20 at 9:16
0

Basically, we can do this thing in 3 ways. Let's see how.

First of all, I want to say that 'JobScheduler' does not give a built in way to ensure a job runs immediately after the constraints are met. We can just use user-triggered tasks that want to run with smallest delay.

  1. You can combine the function with a direct execution check (Just use the function as a fall back)

    void scheduleJob(Context context, int jobId, boolean useUnmetered) { if (areConstraintsMet(context, useUnmetered)) { // Directly run the job MyJobService.executeJob(context, /* any parameters */); } else { // Schedule the job with JobScheduler scheduleJobWithJobScheduler(context, jobId, useUnmetered); } }

    boolean areConstraintsMet(Context context, boolean useUnmetered) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); boolean isUnmetered = activeNetwork != null && !cm.isActiveNetworkMetered();

     return isConnected && (!useUnmetered || isUnmetered);
    

    }

    void scheduleJobWithJobScheduler(Context context, int jobId, boolean useUnmetered) { PersistableBundle extras = new PersistableBundle(); extras.putInt("anExtraInt", someInt); int networkConstraint = useUnmetered ? JobInfo.NETWORK_TYPE_UNMETERED : JobInfo.NETWORK_TYPE_ANY;

     ComponentName componentName = new ComponentName(context, MyJobService.class);
     JobInfo jobInfo = new JobInfo.Builder(jobId, componentName)
             .setRequiredNetworkType(networkConstraint)
             .setExtras(extras)
             .build();
    
     JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
     jobScheduler.schedule(jobInfo);
    

    }

job is executed according to constraints in this.

  1. Just set a latency or deadline function

    JobInfo jobInfo = new JobInfo.Builder(jobId, componentName) .setRequiredNetworkType(networkConstraint) .setMinimumLatency(1000) // 1 second .setOverrideDeadline(30000) // 30 seconds .setExtras(extras) .build();

I set for this, I recommend alteast 1 and not more than 30 seconds.

  1. Go with WorkManager with usage of API

    OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWorker.class) .setConstraints(new Constraints.Builder() .setRequiredNetworkType(useUnmetered ? NetworkType.UNMETERED : NetworkType.CONNECTED) .build()) .build();

    WorkManager.getInstance(context).enqueue(workRequest);

Just try these methods. All the best.

Not the answer you're looking for? Browse other questions tagged or ask your own question.