Created
February 1, 2018 08:16
-
-
Save andhie/4c8a068afd89c1d0dc9428c01d416ee0 to your computer and use it in GitHub Desktop.
Android Priority Job Queue for Firebase JobDispatcher
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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