-
Notifications
You must be signed in to change notification settings - Fork 559
FAQ
Yes, simply use the startNow()
method. Your job will work like an exact job.
private void runJobImmediately() {
new JobRequest.Builder(DemoSyncJob.TAG)
.startNow()
.build()
.schedule();
}
Use the DailyJob
helper class. Notice that the sample job extends DailyJob
and uses DailyJob.schedule(..)
for scheduling the job.
public final class MyDailyJob extends DailyJob {
public static final String TAG = "MyDailyJob";
public static void schedule() {
// schedule between 1 and 6 AM
DailyJob.schedule(new JobRequest.Builder(TAG), TimeUnit.HOURS.toMillis(1), TimeUnit.HOURS.toMillis(6));
}
@NonNull
@Override
protected DailyJobResult onRunDailyJob(Params params) {
return DailyJobResult.SUCCESS;
}
}
Don't use Long.MAX_VALUE
as argument for the execution window. The AlarmManager
doesn't allow setting a start date, instead the execution time is the arithmetic average between start and end date.
Your job might work as expected on Android 5+, but maybe won't run at all on older devices.
// bad, execution time on Android 4.X = startMs + (endMs - startMs) / 2
new JobRequest.Builder(TAG)
.setExecutionWindow(3_000L, Long.MAX_VALUE)
.build()
.schedule();
// better, execution time on Android 4.X is 2 days
new JobRequest.Builder(TAG)
.setExecutionWindow(TimeUnit.DAYS.toMillis(1), TimeUnit.DAYS.toMillis(3))
.build()
.schedule();
This library is a subset of 3 different APIs. Since Android Nougat the minimum interval of periodic jobs is 15 minutes. Although pre Nougat devices support smaller intervals, the least common was chosen as minimum for this library so that periodic jobs run with the same frequency on all devices.
The JobScheduler
with Android Nougat allows setting a smaller interval, but the value is silently adjusted and a warning is being logged. This library throws an exception instead, so that misbehaving jobs are caught early. You can read more about it here.
This library automatically creates a wake lock for you so that the system stays on until your job finished. When your job returns a result, then this wakelock is being released and async operations may not finish. The easiest solution is to not return a result until the async operation finished. Don't forget that your job is already executed on a background thread!
public class AsyncJob extends Job {
@NonNull
@Override
protected Result onRunJob(Params params) {
final CountDownLatch countDownLatch = new CountDownLatch(1);
new Thread() {
@Override
public void run() {
// do async operation here
SystemClock.sleep(3_000L);
countDownLatch.countDown(); // consider doing this in onCancel(), too
}
}.start();
try {
countDownLatch.await();
} catch (InterruptedException ignored) {
}
return Result.SUCCESS;
}
}
You need to be careful, if you remove this dependency after it has been already used for a while.
dependencies {
compile 'com.google.android.gms:play-services-gcm:9.8.0'
}
The reason is that jobs probably were scheduled with the GCM API on Android 4.X and after removing the dependency, the Play Services still look for the platform service, but can't find the class anymore. The result is that your app will crash with a runtime exception similar like this:
java.lang.RuntimeException: Unable to instantiate service com.evernote.android.job.gcm.PlatformGcmService: java.lang.ClassNotFoundException: Didn't find class "com.evernote.android.job.gcm.PlatformGcmService" on path: DexPathList[[zip file "/data/app/com.evernote.android.job.demo-2/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
//or
java.lang.NoClassDefFoundError: Failed resolution of: Lcom/google/android/gms/common/internal/zzbq;
at com.google.android.gms.gcm.GcmNetworkManager.zzhr
java.lang.ClassNotFoundException: Didn't find class "com.google.android.gms.common.internal.zzbq" on path: DexPathList[[zip file "/data/app/.../base.apk"],nativeLibraryDirectories=[/data/app/.../lib/x86, /system/lib, /vendor/lib]]
Fortunately, there is a workaround to prevent the crash. You need to remove the GCM service declaration from the manifest like this and then the Play Services won't try to instantiate the missing class.
<application
...>
<service
android:name="com.evernote.android.job.gcm.PlatformGcmService"
tools:node="remove"/>
</application>
That's expected. The job should run once during a period or within the specified execution window. The timing is a higher requirement than the network type, which is more like a hint when it's best to run your job. To make sure that all requirements are met, you can call .setRequirementsEnforced(true)
. This will make sure that your job won't run, if one check fails, e.g.
new JobRequest.Builder(DemoSyncJob.TAG)
.setExecutionWindow(60_000L, 90_000L)
.setRequiresCharging(true)
.setRequiredNetworkType(JobRequest.NetworkType.UNMETERED)
.setRequirementsEnforced(true)
.build()
.schedule();
There is an alternative. You can register a BroadcastReceiver
to get notified about that you should add your JobCreator
, e.g.
<receiver
android:name=".AddReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.evernote.android.job.ADD_JOB_CREATOR"/>
</intent-filter>
</receiver>
public final class AddReceiver extends AddJobCreatorReceiver {
@Override
protected void addJobCreator(@NonNull Context context, @NonNull JobManager manager) {
manager.addJobCreator(new DemoJobCreator());
}
}
On Android Lollipop or above the JobScheduler
is used for periodic jobs. Android optimizes apps for battery usage, meaning that it tries to save as much power as possible. If your jobs have a high frequency, then it's possible, that some periods are skipped, because the device is saving battery.
You can read more about Doze and App Standby in the official documentation to understand how it works and its implications.
After the app was force killed (or swiped away from the recent list on some devices) Android clears all pending alarms from the AlarmManager
for this app. This is problematic, because until the app is being relaunched alarms can't be rescheduled and jobs won't run. Unfortunately, there is no known workaround.
When the app is being relaunched, this library automatically reschedules pending jobs if necessary. The library also registers a boot completed receiver, so that jobs are rescheduled after a reboot.
Note that only the AlarmManager
is affected. Jobs relying on the JobScheduler
or GcmNetworkManager
still work reliable most of the time. However, some manufacturers could implement custom power saving modes, where even jobs inside of the JobManager
are cleared.
No, that's not possible. The library can't know your process name in advance to start all services in this process. The recommended way is to start your service in the other process from the job.
public class SeparateProcessJob extends Job {
@Override
@NonNull
protected Result onRunJob(final Params params) {
Intent intent = new Intent(getContext(), SeparateProcessService.class);
getContext().startService(intent);
return Result.SUCCESS;
}
}
public class SeparateProcessService extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
// do work
}
}
By default the library prints all log statement in Logcat. But often you wish to store those somewhere else, e.g. in a file. The JobConfig
class gives you an option register a custom logger. It's recommended to add the logger before creating the JobManager
instance.
public class MyLogger implements JobLogger {
@Override
public void log(int priority, @NonNull String tag, @NonNull String message, @Nullable Throwable t) {
// log
}
}
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
JobConfig.addLogger(new MyLogger());
JobManager.create(this).addJobCreator(new DemoJobCreator());
}
}
If you wish, you can even disable printing messages to Logcat, if you own logger handles that for you
JobConfig.setLogcatEnabled(false);
The setPersisted()
method didn't work reliable. Jobs were and are always persisted in a database. Usually you don't want to deal with platform specific issues, e.g. when an alarm is cancelled in the AlarmManager
. The library takes care of this and reschedules jobs if necessary.
If you don't want that a job can run after a reboot, then you need to register your own boot completed broadcast receiver and cancel the specific job yourself.
You can also use a transient job if your job shouldn't run after your process had died.
All job information are stored in a shared preference file called evernote_jobs.xml
and in a database called evernote_jobs.db
. With Android Marshmallow Google introduced the auto backup feature. You should exclude these files so that they aren't backed up.
You can do this by defining a resource XML file (i.e., res/xml/backup_config.xml
) with content:
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="evernote_jobs.xml" />
<exclude domain="database" path="evernote_jobs.db" />
</full-backup-content>
And then referring to it in your application tag in AndroidManifest.xml
:
<application ... android:fullBackupContent="@xml/backup_config">
Yes, you can. You can read more about it at #379 and this sample.