Schedule recurring job using firebase job dispatcher - android

I am trying to post the location of the android device to server every 10 minutes. I am using firebase job dispatcher to do this
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(this));
Job myJob = dispatcher.newJobBuilder()
.setService(UpdateLocationService.class)
.setRecurring(true)
.setTrigger(Trigger.executionWindow(10, 20))
.setRetryStrategy(RetryStrategy.DEFAULT_LINEAR)
.setTag("location-update-job")
.setLifetime(Lifetime.FOREVER)
.build();
dispatcher.mustSchedule(myJob);
UpdateLocationService gets the location and sends to server.
My problem: Things are mostly working fine. Only thing is, the jobs are getting scheduled with a difference of 4m, 6m, 7m, 8m, 10m, 16m, 23m...
Can some one please help me understand going on.
Update: I want the location once in 10-20 minutes. In the above code, the value is too low just for the testing purposes

Also the:
Trigger.executionWindow(windowStart, windowEnd)
expects the windowStart and windowEnd in seconds. As per your requirement, you want the window to be 10 mins. So you should use something like:
Trigger.executionWindow(10*60, 20*60)

There are a few reasons why this could be happening. Firstly is your job returning false in onStopJob()? From the docs
#Override
public boolean onStopJob(JobParameters job) {
return false; // Answers the question: "Should this job be retried?"
}
If the job needs be retried then the backoff will be applied. Combine this with the fact you want it to run again every 10-20 seconds you might get the results you are experiencing.
You have not set any constraints for the job, which also will affect when it will run. e.g.
.setConstraints(
// only run on an unmetered network
Constraint.ON_UNMETERED_NETWORK,
// only run when the device is charging
Constraint.DEVICE_CHARGING
)
Furthermore, I would not use a scheduled job for what you are doing. Look at the Google API Client which offers periodic updates from the fused location provider.
You can implement a callback on your Service or Activity like so
public class MainActivity extends ActionBarActivity implements
ConnectionCallbacks, OnConnectionFailedListener, LocationListener {
...
#Override
public void onLocationChanged(Location location) {
mCurrentLocation = location;
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
updateUI();
}
private void updateUI() {
mLatitudeTextView.setText(String.valueOf(mCurrentLocation.getLatitude()));
mLongitudeTextView.setText(String.valueOf(mCurrentLocation.getLongitude()));
mLastUpdateTimeTextView.setText(mLastUpdateTime);
}
}
Checkout the full docs here but I believe you will have a more consistent experience with services dedicated to what you are trying to achieve.
https://developer.android.com/training/location/receive-location-updates.html

ExecutionWindow specifies approximate time. It's not guaranteed that job will run at the given window. If it misses the window the job will run at earliest time later under ideal circumstances.For recurring jobs once the job has finished next job will calculate execution window time from the time job last run.
LINK
ExecutionWindow represents a Job trigger that becomes eligible once
the current elapsed time exceeds the scheduled time + the {#code windowStart}
value. The scheduler backend is encouraged to use the windowEnd value as a
signal that the job should be run, but this is not an enforced behavior.

Related

Android WorkManager doesn't trigger one of the two scheduled workers

I have two periodic workers scheduled in my app where one worker repeats after 24 hours and another in 15 minutes.
Initially on fresh install things work as expected, but after some days I got an issue on 2 devices(out of 5).
The 24 hour worker is triggered properly but the 15 minute one isn't triggered at all. I have been monitoring this for 24 hours now.
I viewed the databse of workmanager via Stetho and saw some entries for 24-hour worker and 0 entries for 15 minute worker. I'm looking in the WorkSpec table.
I debugged via Android studio and after querying WorkManager using getWorkInfosByTag() I got a list of 80 objects for the 15-minute worker where 79 were in CANCELED state and one was in ENQUEUED state.
So apparently, canceled workers are not added to the DB?
I did not find any document from Google which explains the scenarios in which worker is canceled.
I am using 1.0.0-beta03 version of the work runtime.
Also, I am not killing the app or doing anything funny. The app is running in the background and not being killed.
Devices are Mi A2 (Android 9), Redmi Note 4(Android 7).
I need to understand why is the worker being canceled and is there any better way to debug this? Any pointers will be helpful and upvoted!
Thanks.
Edit1: Posting the code to schedule both workers.
24-hour periodic worker:
public static synchronized void scheduleWork() {
checkPreviousWorkerStatus();
if (isWorking()) {
Log.i("AppDataCleanupWorker", "Did not schedule data cleanup work; already running.");
return;
}
if (lastWorkId != null) {
WorkManager.getInstance().cancelAllWorkByTag("AppDataCleanupWorker");
lastWorkId = null;
}
Constraints constraints = new Constraints.Builder()
.setRequiresCharging(true)
.build();
PeriodicWorkRequest.Builder builder = new PeriodicWorkRequest
.Builder(AppDataCleanupWorker.class, 24, TimeUnit.HOURS)
.addTag("AppDataCleanupWorker")
.setConstraints(constraints);
PeriodicWorkRequest workRequest = builder.build();
lastWorkId = workRequest.getId();
WorkManager.getInstance().enqueue(workRequest);
List<WorkInfo> workInfos = WorkManager.getInstance()
.getWorkInfosByTagLiveData("AppDataCleanupWorker")
.getValue();
if (workInfos != null && workInfos.size() > 1) {
throw new RuntimeException("Multiple workers scheduled. Only one schedule is expected.");
}
}
15-minute periodic worker:
public static synchronized void scheduleWork() {
checkPreviousWorkerStatus();
if (isWorking) {
Log.i("ImageUploadWorker", "Did not schedule image upload work; already running.");
return;
}
if (lastWorkId != null) {
WorkManager.getInstance().cancelAllWorkByTag("ImageUploadWorker");
lastWorkId = null;
}
Constraints constraints = new Constraints.Builder()
.setRequiresBatteryNotLow(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
PeriodicWorkRequest.Builder builder =
new PeriodicWorkRequest.Builder(ImageUploadWorker.class, 15,
TimeUnit.MINUTES)
.addTag("ImageUploadWorker")
.setConstraints(constraints);
PeriodicWorkRequest workRequest = builder.build();
lastWorkId = workRequest.getId();
WorkManager.getInstance().enqueue(workRequest);
List<WorkInfo> workInfos = WorkManager.getInstance()
.getWorkInfosByTagLiveData("ImageUploadWorker").getValue();
if (workInfos != null && workInfos.size() > 1) {
throw new RuntimeException("Multiple workers scheduled. Only one schedule is expected.");
}
}
Note: The device is connected to the Internet & network speed is pretty good.
SOLVED: Worker not being triggered by WorkManager
Resolved the issue after some debugging. Posting here in case someone runs into the same issue.
So, I was canceling and enqueuing workers again and again. So lets say a worker is scheduled for 11.15 AM today, then I cancel and enqueue again, the 11.15 AM slot was not being given to the newly enqueued worker.
Instead, When the 11.15 AM slot is utilised, the work manager just checks that the scheduled worker was canceled and does not trigger the newly enqueued worker.
This was the behaviour on 3 out of 5 devices we tested on. On 2 devices the newly enqueued worker was properly being triggered.
Now the solution:
Remove all code to schedule your workers.
In the onCreate() of your application, first invoke pruneWork() on WorkManager to remove all piled up cancelled worker schedules. Remember the method returns Operation which will help you check the completion of removal.
Before calling pruneWork() you might also call cancelAllWorkByTag() for all your workers to clean up any and all the pending schedules. This method also returns an Operation.
After the work manager schedules are cleared, you can now schedule your PeriodicWorkRequest the way you want. I used enqueueUniquePeriodicWork() to make sure only one instance of worker is running at a time.
Now, my worker is being triggered every 15 minutes properly.
Note that as and when your device sleeps and goes into doze mode, this 15 minute duration will increase.
You can check the work manager database using Stetho library.
The table name is WorkSpec where you'll find all the schedules for your workers. And you can stop app execution at some breakpoint and use getWorkInfosByTag() on WorkManager to get a list of schedules and their current status.
You're doing a few things that are incorrect.
You're using LiveData and calling getValue() on it without adding an Observer. This won't give you what you're looking for - the LiveData never starts tracking the values that you want. Please check out proper LiveData usage here: https://developer.android.com/topic/libraries/architecture/livedata
If you only want one particular copy of a type of work, you should use enqueueUniqueWork instead of enqueue.
Unless you found yourself in an extremely bad situation where you actually need to remove old workers, I would advise you not to call pruneWork(). Please see the documentation: https://developer.android.com/reference/androidx/work/WorkManager#pruneWork()

Android Firebase Jobdispatcher not starting

Hi Android geeks over there,
I am developing an application which uses 'com.firebase:firebase-jobdispatcher:0.8.5' to schedule jobs. But the jobs are not getting executed in the tested device.
While I checked the dumpsys activity service GcmService I got the following log, in which my job is marked as Not yet run.
What would be the possible reason for this behavior?
I also found a status like READY_BATTERY_LOW, is it because my devices is running low on battery? But while the testing is taking place, the device is having 58% battery.
Also, the device is connected to Jio 4g network.
The dumpsys log is included below, could anyone have some comments to give a light on the issue. :)
(scheduled) com.mypackage.testapp/com.firebase.jobdispatcher.GooglePlayReceiver{u=0 tag="test-sync-job" trigger=window{start=300s,end=360s,earliest=-459s,latest=-399s} requirements=[NET_ANY] attributes=[RECURRING] scheduled=-759s last_run=N/A jid=N/A status=READY_BATTERY_LOW retries=0 client_lib=FIREBASE_JOB_DISPATCHER-1}
Not yet run.
Thanks in advance :)
Here is the bit of making a vocation.
Driver = new GooglePlayDriver(context);
firebaseJobDispatcher = new FirebaseJobDispatcher(driver);
Occupation constraintReminderJob = firebaseJobDispatcher.newJobBuilder()
.setService(ReminderService.class)
.setTag(REMINDER_JOB_TAG)
.setConstraints(Constraint.DEVICE_CHARGING)
.setLifetime(Lifetime.FOREVER)
.setRecurring(true)
.setTrigger(Trigger.executionWindow(
REMINDER_INTERVAL_SECONDS,
REMINDER_INTERVAL_SECONDS + SYNC_FLEXTIME_SECONDS
))
.setReplaceCurrent(true)
.assemble();
firebaseJobDispatcher.schedule(constraintReminderJob);
How about we investigate the above scrap.
Occupation
There are a few ascribes to make an occupation.
A string label that (inside your application) particularly recognizes the Job.
A JobService subclass that will contain all the business rationale identified with the Job.
A JobTrigger will establish that the made Job is presently prepared to execute.
An arrangement of Constraints is required keeping in mind the end goal to execute the made activity. As a matter of course, it is unfilled which implies that Job will be kept running when the JobTrigger is initiated.
A RetryStrategy is in charge of taking care of the disappointment conditions. The default is taken care of utilizing exponential backoff procedure.
A lifetime that indicates the season of the activity in which it ought to stay planned. The
default is to keep the Job planned until the following boot.
Package is for client provided additional items. This is a discretionary parameter.
A boolean demonstrates whether the Job should rehash or not. The default is false which implies that the planned Job will execute just once.
A boolean shows whether the booked Job ought to supplant any prior Job
with a similar tag or not. The default an incentive for this boolean banner is false.
Once your activity is prepared, you can utilize plan() strategy to plan the activity.
open static void scheduleJob(Context setting) {
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context));
Occupation work = createJob(dispatcher);
dispatcher.schedule(job);
}
ReminderService.class
open class ReminderService broadens JobService {
/**
* This asynctask will run a vocation once conditions are met with the imperatives
* As soon as client gadget gets associated with the power supply. it will produce
* a warning demonstrating that condition is met.
*/
private AsyncTask mBackgroundTask;
#Override
open boolean onStartJob(final jobParameters) {
mBackgroundTask = new AsyncTask() {
#Override
ensured Object doInBackground(Object[] objects) {
Setting = ReminderService.this;
ReminderTasks.executeTasks(context, ReminderTasks.ACTION_CHARGING_REMINDER);
Log.i("TAG", "onStartJob");
return invalid;
}
#Override
secured void onPostExecute(Object o) {
/* false means, that activity is finished. we would prefer not to reschedule it*/
jobFinished(jobParameters, false);
Log.i("TAG", "onStartJob-OnPost");
}
};
mBackgroundTask.execute();
return genuine;
}
#Override
open boolean onStopJob(JobParameters jobParameters) {
on the off chance that (mBackgroundTask != invalid) {
mBackgroundTask.cancel(true);
}
Log.i("TAG", "onStopJob");
/* genuine means, we're not done, if you don't mind reschedule */
return genuine;
}
}
There are three strategies that will be utilized while utilizing FireBase Job Dispatcher.
onStartJob(JobParameters params)- This is the underlying strategy that will be conjured when a vocation is called. It keeps running on the fundamental string. It will restore a Boolean which tells whether an occupation is remaining or not. Returning genuine demonstrates that more work is remaining. We can call jobFinished() when the activity is finished.
onStopJob(JobParameters params)- This technique is considered when your activity is halted. The activity can be halted because of different reasons if the running limitations related to the activity are never again fulfilled. It will restore a Boolean which tells whether work ought to be attempted again or not. On the off chance that returned genuinely, at that point, the system will set up this activity again for execution.
jobFinished(JobParameters params, boolean needsReschedule)- When work has been offloaded to another string, it ought to be called expressly.

How to run service every 30 mins forever using Firebase JobDispatcher?

Till now, I have used AlarmManager to send HTTP request every 30 minutes. But recently, I faced warning in Google Play Console: Excessive Wakeups. When I read warning details, it said that AlarmManager waking devices up excessively.
Then I researched on what else I could use to send request every 30 minutes. In result, I found this documentation. It recommends to use JobScheduler or JobDispatcher. First of all, I tried to use JobScheduler - but it required API 21 which is not okey for me. I need to support devices from API 16 in my current project. Then I decided to use JobDispatcher.
This is my JobService:
public class MyJobService extends JobService {
#Override
public boolean onStartJob(final JobParameters params) {
Log.d("JobDispatcherLog", params.getTag()+ " STARTED");
jobFinished(params, true);
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
Log.d("JobDispatcherLog", params.getTag()+ " STOPPED");
return false;
}
}
This is where I am creating job which is supposed to run service every 30 minutes.
FirebaseJobDispatcher dispatcher =
new FirebaseJobDispatcher(new GooglePlayDriver(this));
RetryStrategy retryStrategy =
dispatcher.newRetryStrategy(
RetryStrategy.RETRY_POLICY_LINEAR, 1800, 86400);
Job job = dispatcher.newJobBuilder()
.setService(MyJobService.class)
.setTag("very-important-job")
.setLifetime(Lifetime.FOREVER)
.setReplaceCurrent(true)
.setRetryStrategy(retryStrategy)
.setConstraints(Constraint.ON_ANY_NETWORK)
.build();
dispatcher.mustSchedule(job);
I tested this code and got following result in LogCat:
03-16 18:01:08.540 D/JobDispatcherLog: very-important-job STARTED
03-16 18:43:41.747 D/JobDispatcherLog: very-important-job STARTED
03-16 20:12:01.361 D/JobDispatcherLog: very-important-job STARTED
As you can see it is not running every 30 minutes.
My question: Is this normal behaviour for JobDispatcher? How to run service exactly every 30 minutes? If it is not possible, what else I can use to implement before stated function?
You can reduce the time window in which your job could be triggered. It can help you to control a little the time margin in which your job must trigger.
// Create a new FirebaseJobDispatcher with the driver
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(driver);
// Building the Job.
Job yourJob = dispatcher.newJobBuilder()
.setService(YourJobService.class) // The Job service that will execute.
.setTag(YOUR_JOB_TAG) // Unique Tag.
.setLifetime(Lifetime.FOREVER) // To make it last "forever".
.setRecurring(true) // To make it repetitive
/* setTrigger is your time window. You can adjust the SYNC_FLEXTIME_SECONDS to reduce the window a little. But it is not guaranteed to be .*/
.setTrigger(Trigger.executionWindow(ALARM_INTERVAL_SECONDS, ALARM_INTERVAL_SECONDS + SYNC_FLEXTIME_SECONDS))
.setReplaceCurrent(true) // To make it to overwrite a previous job created with the same tag.
.build();
NOTE: Be aware that if your are going to use FirejobDispatcher (or JobScheduler) then there will not be guarantees that your service will trigger exactly each 30 min.
If you want a more precise "alarm" then you must incline in using AlarmManager + Foreground Service. Be aware that AlarmManager for Android 6 or later will have a little delay (in other words the alarm will not trigger in exactly 30 minutes).

Is it possible to re-schedule job within your job

I'm using Evernote's JobScheduler. The library provides a way to set a periodic job, however that has a minimum interval of 15 minutes between repetitions.
I want to be able to send a user's location to the server every few minutes (maximum 3, minimum 2). Is it technically OK to schedule your old job again and again after the job is done? Something like this:
protected Result onRunJob(Params params)
{
// my actual job runs here
schedulePeriodicJob();
return Result.SUCCESS;
}
private void schedulePeriodicJob()
{
jobId = new JobRequest.Builder(ScheduleJob.TAG)
.setExact(TimeUnit.MINUTES.toMillis(2))
.setBackoffCriteria(TimeUnit.MINUTES.toMillis(1), JobRequest.BackoffPolicy.LINEAR)
.build()
.schedule();
}
Or should I simply use a foreground service to achieve this?
Evernote job scheduler is based upon JobScheduler, GcmNetworkManager or AlarmManager depending on the context/device/playservices version. Whatever the implementation, APIs exposes the following method:
public JobRequest.Builder setPeriodic(long intervalMs, long flexMs)
I would use this one instead of "manually" rescheduling job. I also don't think your setBackoffCriteria call would not work without setting recurring job at APIs level.

Change the period of a periodic task in Firebase JobDispatcher?

I was using GCM network manager, but then I heard that Firebase JobDispatcher includes GCM plus other features so I'm trying to use that.
I have successfully programmed a periodic task and it works fine, but the problem is that I need the period to change and not be fixed from the beginning.
The reason for that is, I'm using an activity recognition service and I want the next time the JobDispatcher executes the periodic task to be based on the detected current activity. For example if you're walking, the next time the task is triggered is after 30 minutes, while if you're in a car then the period is 5 minutes (mainly because if you're in a car it's more likely that your phone will provide different location values in a short while compared to when you're on foot).
This is the way I program the periodic task, as you can see I'm setting a fixed value, I want to know if the service that is triggered by this task can provide a feedback that'll change the period of the task.
final Builder builder = jobDispatcher.newJobBuilder()
.setTag(form.tag.get())
.setRecurring(form.recurring.get())
.setLifetime(form.persistent.get() ? Lifetime.FOREVER : Lifetime.UNTIL_NEXT_BOOT)
.setService(DemoJobService.class)
.setTrigger(Trigger.executionWindow(
form.getWinStartSeconds(), form.getWinEndSeconds()))
.setReplaceCurrent(form.replaceCurrent.get())
.setRetryStrategy(jobDispatcher.newRetryStrategy(
form.retryStrategy.get(),
form.getInitialBackoffSeconds(),
form.getMaximumBackoffSeconds()));
if (form.constrainDeviceCharging.get()) {
builder.addConstraint(Constraint.DEVICE_CHARGING);
}
if (form.constrainOnAnyNetwork.get()) {
builder.addConstraint(Constraint.ON_ANY_NETWORK);
}
if (form.constrainOnUnmeteredNetwork.get()) {
builder.addConstraint(Constraint.ON_UNMETERED_NETWORK);
}
To do this you'd need to have your onStartJob in DemoJobService.class cancel itself using your tag that you've saved in SharedPrefs / elsewhere (form.tag.get() in your example):
FirebaseJobDispatcher(GooglePlayDriver(context)).cancel(yourTag)
It can then reschedule the job with the updated parameters. Unfortunately there's currently no way to edit jobs, they must be cancelled and recreated!

Categories

Resources