Skip to content

Instantly share code, notes, and snippets.

@andhie
Created February 1, 2018 08:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save andhie/4c8a068afd89c1d0dc9428c01d416ee0 to your computer and use it in GitHub Desktop.
Save andhie/4c8a068afd89c1d0dc9428c01d416ee0 to your computer and use it in GitHub Desktop.
Android Priority Job Queue for Firebase JobDispatcher
package com.birbit.android.jobqueue.scheduling;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.birbit.android.jobqueue.JobManager;
import com.birbit.android.jobqueue.log.JqLog;
import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService;
public abstract class FirebaseJobDispatcherService extends JobService {
/**
* Creates a scheduler for the given service.
* Keep in mind that there is a strict 1-1 mapping between the created scheduler and the
* service. You should pass the returned scheduler to the JobManager configuration.
*
* @param appContext The application context
* @param klass The service implementation that extends FirebaseJobDispatcherService.
* @return A scheduler that is associated with the given service class.
*/
@SuppressWarnings("unused")
public static FirebaseScheduler createSchedulerFor(
@SuppressWarnings("UnusedParameters") Context appContext,
Class<? extends FirebaseJobDispatcherService> klass) {
if (FirebaseJobDispatcherService.class == klass) {
throw new IllegalArgumentException("You must create a service that extends FirebaseJobDispatcherService");
}
return new FirebaseScheduler(appContext.getApplicationContext(), klass);
}
@Override
public void onCreate() {
super.onCreate();
FirebaseScheduler scheduler = getScheduler();
if (scheduler != null) {
scheduler.setJobService(this);
} else {
JqLog.e("FirebaseJobDispatcherService has been created but it does not have a" +
" scheduler. You must initialize JobManager before the service is created.");
}
}
@Override
public boolean onStartJob(JobParameters params) {
FirebaseScheduler scheduler = getScheduler();
if (scheduler != null) {
return scheduler.onStartJob(params);
}
JqLog.e("FirebaseJobDispatcherService has been triggered but it does not have a" +
" scheduler. You must initialize JobManager before the service is created.");
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
FirebaseScheduler scheduler = getScheduler();
if (scheduler != null) {
return scheduler.onStopJob(params);
}
JqLog.e("FirebaseJobDispatcherService has been stopped but it does not have a" +
" scheduler. You must initialize JobManager before the service is created.");
return false;
}
@Nullable
private FirebaseScheduler getScheduler() {
Scheduler scheduler = getJobManager().getScheduler();
if (scheduler instanceof FirebaseScheduler) {
return (FirebaseScheduler) scheduler;
}
JqLog.e("FirebaseJobDispatcherService has been created but the JobManager does not" +
" have a scheduler created by FirebaseJobDispatcherService.");
return null;
}
/**
* Return the JobManager that is associated with this service
*
* @return The JobManager that is associated with this service
*/
@NonNull
protected abstract JobManager getJobManager();
}
package com.birbit.android.jobqueue.scheduling;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import com.birbit.android.jobqueue.Params;
import com.birbit.android.jobqueue.log.JqLog;
import com.birbit.android.jobqueue.network.NetworkUtil;
import com.firebase.jobdispatcher.Constraint;
import com.firebase.jobdispatcher.FirebaseJobDispatcher;
import com.firebase.jobdispatcher.GooglePlayDriver;
import com.firebase.jobdispatcher.Job;
import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService;
import com.firebase.jobdispatcher.Lifetime;
import com.firebase.jobdispatcher.Trigger;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
class FirebaseScheduler extends Scheduler {
private static final String TAG = "FirebaseScheduler";
private static final String KEY_UUID = "uuid";
private static final String KEY_DELAY = "delay";
private static final String KEY_NETWORK_STATUS = "networkStatus";
private static final String KEY_DEADLINE = "keyDeadline";
private FirebaseJobDispatcher jobDispatcher;
// set when service invokes, cleared when service dies
@Nullable
private JobService jobService;
final Class<? extends FirebaseJobDispatcherService> serviceClass;
FirebaseScheduler(Context context, Class<? extends FirebaseJobDispatcherService> serviceClass) {
this.serviceClass = serviceClass;
jobDispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context.getApplicationContext()));
}
void setJobService(@Nullable JobService jobService) {
this.jobService = jobService;
}
@Override
public void request(SchedulerConstraint constraint) {
if (JqLog.isDebugEnabled()) {
JqLog.d("creating gcm wake up request for %s", constraint);
}
Job.Builder builder = jobDispatcher.newJobBuilder()
.setTag(constraint.getUuid())
.setLifetime(Lifetime.FOREVER)
.setService(serviceClass)
.setExtras(toBundle(constraint));
switch (constraint.getNetworkStatus()) {
case NetworkUtil.UNMETERED:
builder.setConstraints(Constraint.ON_UNMETERED_NETWORK);
break;
case NetworkUtil.METERED:
builder.setConstraints(Constraint.ON_ANY_NETWORK);
break;
default:
builder.setConstraints(Constraint.DEVICE_IDLE);
break;
}
long endTimeMs = constraint.getOverrideDeadlineInMs() == null
? constraint.getDelayInMs() + TimeUnit.SECONDS.toMillis(getExecutionWindowSizeInSeconds())
: constraint.getOverrideDeadlineInMs();
long executionStart = TimeUnit.MILLISECONDS.toSeconds(constraint.getDelayInMs());
long executionEnd = TimeUnit.MILLISECONDS.toSeconds(endTimeMs);
// JobManager uses MS vs Firebase JobDispatcher uses seconds so we must check to avoid illegal arg exceptions
if (executionEnd <= executionStart) {
executionEnd = executionStart + 1;
}
builder.setTrigger(Trigger.executionWindow((int) executionStart, (int) executionEnd));
jobDispatcher.mustSchedule(builder.build());
}
/**
* If this scheduling request is made for a Job with a deadline, this method is NOT called.
*
* @return The execution window time for the Job request
*/
long getExecutionWindowSizeInSeconds() {
// let jobs timeout in a week
return TimeUnit.DAYS.toSeconds(7);
}
@Override
public void onFinished(SchedulerConstraint constraint, boolean reschedule) {
JqLog.d("[Firebase Scheduler] on finished job %s. reschedule:%s", constraint, reschedule);
JobService service = this.jobService;
if (service == null) {
JqLog.e("[Firebase Scheduler] scheduler onFinished is called but i don't have a job service");
return;
}
Object data = constraint.getData();
if (data instanceof JobParameters) {
JobParameters params = (JobParameters) data;
jobService.jobFinished(params, reschedule);
} else {
JqLog.e("[Firebase Scheduler] cannot obtain the job parameters");
}
}
@Override
public void cancelAll() {
JqLog.d("[Firebase Scheduler] cancel");
jobDispatcher.cancel(TAG);
}
static Bundle toBundle(SchedulerConstraint constraint) {
Bundle bundle = new Bundle();
// put boolean is api 22
if (constraint.getUuid() != null) {
// gcm throws an exception if this is null
bundle.putString(KEY_UUID, constraint.getUuid());
}
bundle.putInt(KEY_NETWORK_STATUS, constraint.getNetworkStatus());
bundle.putLong(KEY_DELAY, constraint.getDelayInMs());
if (constraint.getOverrideDeadlineInMs() != null) {
bundle.putLong(KEY_DEADLINE, constraint.getOverrideDeadlineInMs());
}
return bundle;
}
@VisibleForTesting
static SchedulerConstraint fromBundle(Bundle bundle) throws Exception {
SchedulerConstraint constraint = new SchedulerConstraint(bundle.getString(KEY_UUID));
if (constraint.getUuid() == null) {
// backward compatibility
constraint.setUuid(UUID.randomUUID().toString());
}
constraint.setNetworkStatus(bundle.getInt(KEY_NETWORK_STATUS, NetworkUtil.DISCONNECTED));
constraint.setDelayInMs(bundle.getLong(KEY_DELAY, 0));
if (bundle.containsKey(KEY_DEADLINE)) {
constraint.setOverrideDeadlineInMs(bundle.getLong(KEY_DEADLINE, Params.FOREVER));
}
return constraint;
}
boolean onStartJob(JobParameters params) {
SchedulerConstraint constraint;
try {
constraint = fromBundle(params.getExtras());
} catch (Exception e) {
JqLog.e(e, "bad bundle from framework job scheduler start callback.");
return false;
}
JqLog.d("[Firebase Scheduler] start job %s", constraint);
constraint.setData(params);
return start(constraint);
}
boolean onStopJob(JobParameters params) {
SchedulerConstraint constraint;
try {
constraint = fromBundle(params.getExtras());
} catch (Exception e) {
JqLog.e(e, "bad bundle from job scheduler stop callback");
return false;
}
return stop(constraint);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment