What tools are available to test JobScheduler?
Asked Answered
P

4

67

We're implementing a Job via JobScheduler for background loading of data. The job will fire about once a day. What tools are available for us to test this functionality (possibly ADB)?

Use cases are to be able to simulate the conditions required for a Job to be run or to just say specifically "Run this job" as part of our automated test suite.

Postgraduate answered 10/8, 2016 at 18:28 Comment(0)
H
131

Right.
Henning and P4u144 set me on the right track to answer this in more detail.

Identify all registered jobs

Identify your task with the adb shell dumpsys jobscheduler command.
This will give you a huge output in following categories.

  • Settings
  • Registered XX Jobs
  • Connectivity
  • Alarms
  • Idle
  • Battery
  • AppIdle
  • Content
  • Job history
  • Pending queue

The category you are most likely to be interested in is Registered XX Jobs. This tells you how many jobs have been scheduled on the device.
For example, your packagename is com.foo.bar.application you should see an entry like this:

JOB #u0a93/17: eec3709 com.foo.bar.application/com.evernote.android.job.v21.PlatformJobService
    u0a93 tag=*job*/com.foo.bar.application/com.evernote.android.job.v21.PlatformJobService
    Source: uid=u0a93 user=0 pkg=com.foo.bar.application
    JobInfo:
      Service: com.foo.bar.application/com.evernote.android.job.v21.PlatformJobService
      PERIODIC: interval=+15m0s0ms flex=+5m0s0ms
      PERSISTED
      Requires: charging=false deviceIdle=false
      Network type: 2
      Backoff: policy=1 initial=+30s0ms
      Has early constraint
      Has late constraint
    Required constraints: TIMING_DELAY DEADLINE UNMETERED
    Satisfied constraints: CONNECTIVITY NOT_ROAMING APP_NOT_IDLE DEVICE_NOT_DOZING
    Unsatisfied constraints: TIMING_DELAY DEADLINE UNMETERED
    Earliest run time: 07:23
    Latest run time: 12:23
    Ready: false (job=false pending=false active=false user=true)

Tip: Use adb shell dumpsys jobscheduler | grep com.foo.bar.application to quickly filter the list.

Now you can easily identify if your job has been registered with the correct criteria.

FireBaseJobdispatcher

If you use FirebaseJobDispatcher lib you can use

adb shell dumpsys activity service GcmService | grep com.foo.bar.debug
    com.foo.bar.debug:0 v853
    u0|com.foo.bar.debug: 3
    (scheduled) com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver{u=0 tag="com.foo.bar.debug.job.FetchArticlesJob" trigger=window{start=10800s,end=11700s,earliest=10448s,latest=11348s} requirements=[NET_UNMETERED,DEVICE_IDLE] attributes=[PERSISTED,RECURRING] scheduled=-351s last_run=N/A jid=N/A status=PENDING retries=0 client_lib=FIREBASE_JOB_DISPATCHER-1}
    (scheduled) com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver{u=0 tag="com.foo.bar.debug.job.FetchNotificationGroupsJob" trigger=window{start=86400s,end=129600s,earliest=86048s,latest=129248s} requirements=[NET_CONNECTED,CHARGING] attributes=[PERSISTED,RECURRING] scheduled=-351s last_run=N/A jid=N/A status=PENDING retries=0 client_lib=FIREBASE_JOB_DISPATCHER-1}
    (scheduled) com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver{u=0 tag="com.foo.bar.debug.job.RemoveUnusedRealmArticlesJob" trigger=window{start=577980s,end=608400s,earliest=521961s,latest=552381s} requirements=[NET_ANY] attributes=[PERSISTED,RECURRING] scheduled=-56018s last_run=N/A jid=N/A status=PENDING retries=0 client_lib=FIREBASE_JOB_DISPATCHER-1}
    (finished) [com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver:com.foo.bar.debug.job.UpdateNotificationGroupJob,u0]
    (finished) [com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver:com.foo.bar.debug.job.UpdatePushTokenJob,u0]
    (finished) [com.foo.bar.debug/com.firebase.jobdispatcher.GooglePlayReceiver:com.foo.bar.debug.job.FetchArticlesJob,u0]

to check if your service has been scheduled or run.

Force your task to run

When creating a Job you get a JOB_ID returned.
Use this JOB_ID to force the job to run.
You can do this by using the adb shell cmd jobscheduler run command, (requires Android 7.1 or higher).

For example, your packagename is com.foo.bar.application and the JOB_ID was 1. You can now run your task via adb

adb shell cmd jobscheduler run -f com.foo.bar.application 1

Don't forget the -f option as this forces the job to run even if the restrictions set are not met.

Evernote Android Job Lib

Last but certainly not least.
Use the wonderful library from Evernote for this.
It allows for easy backporting of the JobScheduler on lower API levels using either JobScheduler, GcmNetworkManager or AlarmManager depending on your API level.

Edit 24/08

Even better use the firebase job dispatcher library.

The Firebase JobDispatcher is a library for scheduling background jobs in your Android app. It provides a JobScheduler-compatible API that works on all recent versions of Android (API level 9+) that have Google Play services installed.

I hope this helped.

Thanks

Edit 28/04/2019

Both Evernote Android-Job and Firebase JobDispatcher are now in maintenance only mode and they both suggest to use jetpack's WorkManager for these kind of jobs.

Edit 10/06/202

Android Jetpack WorkManager made it slightly easier to diagnose.
First you will need to enable diagnostics for your package

adb shell am broadcast -a 'androidx.work.diagnostics.REQUEST_DIAGNOSTICS' -p 'com.foo.bar.application'

After that you can WorkManager will dump information in ADB Logcat which you can observe by entering.

adb logcat

Or via Android Studio Logcat tool window

Healing answered 9/2, 2017 at 9:50 Comment(9)
Good summary! I just wanted to add that all you wrote applies to Android 7 and up. JobScheduler is on board since Android 5, there the output is different. Just if anybody wonders.Javanese
In my case, Firebase JobDispatcher used GcmNetworkManager to schedule jobs, even on Android O, instead of platform available JobScheduler -- so I couldn't use this command to force the job to run. Had to switch to evernote's android-job, which does use platform JobScheduler on api 21+.Lorsung
The output does says Running job [FORCED] but the job doesn't run. Tested on emulator with with Android O.Bimolecular
In JOB #u0a93/17 JOB_ID is 17 ?Pewit
@MuhammadBabar I experience the same with Android 8.1 and 9 (AVD & Device) but it also shuts down logcat output, leaving a blank window. I had to restart AS after that.Pewit
@MuhammadBabar did you solve the problem with Running job [FORCED]?Periphery
@LeandroOcampo Sorry it's being so long that I don't remember exactly.Bimolecular
there is now a gui tool in Android Studio to analyze the WorkManager: developer.android.com/studio/preview/…Javanese
FYI, your edit "Edit 10/06/202" is missing the 2 at the end for 2022.Tiu
J
19

// Update: There is a new GUI tool in Android Studio 2020.3.1: https://developer.android.com/studio/preview/features#workmanager-inspector

// Old answer: With the command adb shell dumpsys jobscheduler you get informations about the currently scheduled and active jobs.

I noticed that the ouput from the command differs greatly between Android 6 and 7. With an Android 5 device the output is very short and sometimes cryptic. The interesting part with the registered jobs is build here and repeated below for convenience, which should help with the deciphering:

@Override
public String toString() {
    return String.valueOf(hashCode()).substring(0, 3) + ".."
            + ":[" + job.getService()
            + ",jId=" + job.getId()
            + ",u" + getUserId()
            + ",R=(" + formatRunTime(earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME)
            + "," + formatRunTime(latestRunTimeElapsedMillis, NO_LATEST_RUNTIME) + ")"
            + ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging()
            + ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures
            + ",P=" + job.isPersisted()
            + (isReady() ? "(READY)" : "")
            + "]";
}

Android 7 devices on the other hand have a very long output with more detailed and better readable informations. Also there are more features like a history. The drawback is that you have to find the interesting parts first.

I haven't found a way to force a job to run though, there is a feature request for it. See the answer from p4u144.

Javanese answered 15/8, 2016 at 14:15 Comment(1)
Henning: The link to the "Android 6" code you have actually points to the lollipop-release branch, which is slightly different from the marshmallow branch's output.Epergne
V
17

Starting in Android 7.0, there is a new cmd for adb shell. And in 7.1 (only in preview right now), the adb shell cmd jobscheduler has been added as you can see here to force run your JobScheduler. The helps says:

Job scheduler (jobscheduler) commands: help Print this help text.

run [-f | --force] [-u | --user USER_ID] PACKAGE JOB_ID Trigger immediate execution of a specific scheduled job. Options: -f or --force: run the job even if technical constraints such as connectivity are not currently met -u or --user: specify which user's job is to be run; the default is the primary or system user

Vitrification answered 14/11, 2016 at 16:16 Comment(0)
L
-1

This is working for me, without the need to use adb commands. It requires minSdk 21

@RunWith(AndroidJUnit4.class)
@TargetApi(VERSION_CODES.LOLLIPOP)
public abstract class BaseJobServiceTest {

  protected final Context context() {
    return InstrumentationRegistry.getTargetContext();
  }

  protected final void launchJobAndWait(JobInfo jobInfo) throws InterruptedException {
    JobScheduler scheduler = (JobScheduler) context().getSystemService(Context.JOB_SCHEDULER_SERVICE);

    scheduler.schedule(jobInfo);

    while (jobExecutionPending(scheduler, jobInfo)) {
      Thread.sleep(50);
    }
  }

  private boolean jobExecutionPending(JobScheduler scheduler, JobInfo jobInfo) {
    if (VERSION.SDK_INT >= VERSION_CODES.N) {
      return scheduler.getPendingJob(jobInfo.getId()) != null;
    }

    List<JobInfo> scheduledJobs = scheduler.getAllPendingJobs();
    for (int i = 0, size = scheduledJobs.size(); i < size; i++) {
      if (scheduledJobs.get(i).getId() == jobInfo.getId()) {
        return true;
      }
    }

    return false;
  }
}
Luigiluigino answered 14/3, 2018 at 15:53 Comment(3)
For periodic jobs, won't scheduler.getPendingJob() always return non-null?Epergne
might be the case, I'm not using that right now so I can't say. Maybe there's a way to identify if it's a new JobInfo? I don't know if equals will do the job.Luigiluigino
This is a test class, we want to force the main thread to go to sleep.Luigiluigino

© 2022 - 2024 — McMap. All rights reserved.