in my app i have set a periodic job that is set to run every 30 minutes.
The first job run occurs right when I do schedule that periodic job, which is not wanted in my case.
What I want is to skip the first run so that it will run for the first time after 30+ minutes.
My two thoughts on how to approach this was to either have it not run at all for the first 30 minutes somehow (some kind of delay), or mark the first job run as done before even having the chance to start.
Unfortunately I have not found any method in JobInfo that would allow me to do any of those.
Another workaround that would fulfill my needs would be to somehow limit the jobs to only occur while app is in the background. It does not entirely solve the issue but it could serve as a workaround in my case.
Following is my current code for scheduling the periodic job:
private void scheduleJob() {
ComponentName componentName = new ComponentName(this, myRecurringTask.class);
JobInfo info = new JobInfo.Builder(JOB_ID, componentName)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPersisted(true)
.setPeriodic(1800000)
.build();
JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
scheduler.schedule(info);
}
I hope someone has run into the same situation and can help me resolve it... Thank you!
Use WorkManager for scheduling backgound work, see introduction here.
1. Add Dependency:
implementation "androidx.work:work-runtime-ktx:2.4.0"
2. Create Worker Class:
class DataRefresher(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result { //will run on background thread
//your logic
return try {
//your logic
Result.success()
} catch (e: HttpException) {
Result.retry()
}
}
}
3. Create Application Class:
class DevBytesApplication : Application() {
private val backgroundScope = CoroutineScope(Dispatchers.Default) //standard background thread
override fun onCreate() { //called when app launches, same as Activity
super.onCreate()
initWork()
}
private fun initWork() {
backgroundScope.launch { //run in background, not affecting ui
setupDataRefreshingWork()
}
}
#SuppressLint("IdleBatteryChargingConstraints")
private fun setupDataRefreshingWork() {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) //when using wifi
.setRequiresBatteryNotLow(true)
.setRequiresCharging(true)
.setRequiresDeviceIdle(true) //when not running heavy task
.build()
val repeatingRequest = PeriodicWorkRequestBuilder<DataRefresher>(1, TimeUnit.DAYS) //【15 minutes is minimum!!】
.setConstraints(constraints)
.setInitialDelay(30, TimeUnit.MINUTES) //【initial delay!!】
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
DataRefresher::class.java.simpleName, //work name
ExistingPeriodicWorkPolicy.KEEP, //if new work comes in with same name, discard it
repeatingRequest
)
}
}
4. Setup AndroidManifest:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.devbytestest">
<application
android:name=".DevBytesApplication" //【here, must!!!】
...
</application>
</manifest>
Related
First let me correct please :
Work Manager : The minimum repeat interval that can be defined is 15 minutes (same as the JobScheduler API).
If this is not correct please let me know.
I have created below class for executing periodic work request :
object WorkManagerUtils {
fun syncWorkManager() {
val myConstraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val syncRequest = PeriodicWorkRequest
.Builder(MyWorker::class.java, 20000, TimeUnit.MILLISECONDS)
.setConstraints(myConstraints)
.build()
WorkManager
.getInstance()
.enqueueUniquePeriodicWork(
Constants.WORKER,
ExistingPeriodicWorkPolicy.KEEP,
syncRequest)
}
}
Below is my Worker class. Please check :
class MyWorker(val context: Context, param: WorkerParameters) : Worker(context, param) {
override fun doWork(): Result {
if (isNetworkAvailable(context)) {
callSyncApi()
} else {
WorkManagerUtils.syncWorkManager()
}
return Result.success()
}
private fun callSyncApi() {
ToastUtils.shortToast(0,"This is working")
}
}
Calling this in my Activity as below :
WorkManagerUtils.syncWorkManager()
You can notice that currently I am just displaying toast as my work. I want to check that is this working or not ?
But the toast is not displaying.
Any interval under 15 minutes will be replaced by 15 minutes.
Assuming your ToastUtils.showToast(...) works, I believe work manager chose 15 minutes of interval and the “KEEP” existing work policy prevented rescheduling and testing.
I suggest while testing change the existing work policy to “REPLACE”.
From work manager 1.0.0 source:
public final class PeriodicWorkRequest extends WorkRequest {
...
public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes
...
}
public class WorkSpec {
...
public void setPeriodic(long intervalDuration) {
if (intervalDuration < MIN_PERIODIC_INTERVAL_MILLIS) {
...
IntervalDuration = MIN_PERIODIC_INTERVAL_MILLIS;
}
}
...
}
There is an overload of setPeriodic function which applies the same interval enforcement.
I'm using WorkManager API (version 2.4.0) to create simple periodic running tasks. Here is the worker class
class BackupWorker(
context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result = coroutineScope {
Log.i(TAG, "Starting worker")
makeNotification(
"Preparing worker",
applicationContext
)
sleep() //simulate long running task
for(i in 0..20) {
makeNotification(
"Firing update $i",
applicationContext,
true
)
sleep()
}
makeNotification(
"Worker complete",
applicationContext
)
Log.i(TAG, "Finishing backup worker")
Result.success()
}
}
And the work request is set up as follows
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresStorageNotLow(true).build();
PeriodicWorkRequest workRequest =
new PeriodicWorkRequest.Builder(BackupWorker.class,
15, TimeUnit.MINUTES)
.setConstraints(constraints)
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork("tag_worker",
ExistingPeriodicWorkPolicy.REPLACE,
workRequest);
The work request is being correctly picked up by the OS. However, if I turn Wi-Fi off whilst running, it doesn't stop and continues to run what's inside doWork() which in fact contradicts the whole purpose of this API.
Is there anything missing here? Any thoughts?
I have such code, I need to implement the task queue, if the task is in the queue, then you do not need to add it.
I implemented as shown in when, everything works, but sometimes the state of the worker remains ENQUEUED, and new tasks are not added to the queue.
That is, when there is no Internet, I add a task, when the Internet appears, tasks begin to run out, but for some reason, sometimes it doesn’t happen, I can’t understand why the task does not start despite the fact that the Internet is there and the task is in the queue.
How can you determine why the task will not start?
Does anyone have a better suggestion?
//run task
runOneTimeWorkByType<GetDocumentsWorker>(GET_DOCUMENTS_TAG)
private inline fun <reified W : Worker> runOneTimeWorkByType(tag: String) {
val workerInfoList = workManager
.getWorkInfosByTag(tag)
.get()
for (item in workerInfoList) {
if (item.state == WorkInfo.State.ENQUEUED){
return
}
}
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workRequest =
OneTimeWorkRequestBuilder<W>()
.setConstraints(constraints)
.addTag(tag)
.build()
workManager.enqueue(workRequest)
}
class GetDocumentsWorker(ctx: Context, workerParams: WorkerParameters) :
Worker(ctx, workerParams) {
#Inject
lateinit var serviceUtils: ServiceUtils
init {
App.appComponent.inject(this)
}
override fun doWork(): Result {
Log.d("workmng", "GetDocumentsWorker: start")
try {
serviceUtils.documentsGet()
} catch (e: Exception) {
Log.d("workmng", "GetDocumentsWorker: exception", e.cause)
return Result.retry()
}
Log.d("workmng", "GetDocumentsWorker: end")
return Result.success()
}
}
UPDATE:
I tried to start the task without conditions, but in this case, nothing starts either, have ideas why so?
fun runGetDocumentsTask() {
val workRequest =
OneTimeWorkRequestBuilder<GetDocumentsWorker>()
.addTag(GET_DOCUMENTS_TAG)
.build()
workManager.enqueue(workRequest)
}
Everything starts to work fine when I cancel jobs: workManager.cancelAllWork()
When creating a worker, I run several periodic tasks, can there be a problem in them? If so, how to fix it?
private var workManager: WorkManager = WorkManager.getInstance(ctx)
init {
//workManager.cancelAllWork()
runSendAllPeriodicTasks()
}
private fun runSendAllPeriodicTasks() {
runOneTimeWorkOnPeriod<SendAllWorker>(15, TimeUnit.MINUTES)
runOneTimeWorkOnPeriod<FailureFilesResendWorker>(3, TimeUnit.HOURS)
runOneTimeWorkOnPeriod<GetItemsWorker>(1, TimeUnit.HOURS)
}
I have implemented Evernote Android Job in my android application. but i want to change it as WorkManager.
JobManager.create(this).addJobCreator(new MyJob());
public class MyJob implements JobCreator {
#Nullable
#Override
public Job create(#NonNull String tag) {
switch (tag) {
case SyncMasterDataJOB.TAG:
return new SyncMasterDataJOB();
}
return null;
}
}
Job Class:
public class SyncMasterDataJOB extends Job {
public static final String TAG = "job_note_sync";
#NonNull
#Override
protected Result onRunJob(#NonNull Params params) {
return Result.SUCCESS;
}
public static void schedulePeriodic() {
try{
new JobRequest.Builder(SyncMasterDataJOB.TAG)
.setPeriodic(15*1000, 5*1000)
.setUpdateCurrent(true)
.build()
.schedule();
} catch (Exception e){
e.printStackTrace();
}
}
How can i change Job into android workmanager.
WorkManager is highly configurable and will allow you to create a PeriodicWorkRequest or a OneTimeWorkRequest these are guaranteed to succeed. PeriodicWorkRequest will fire when you schedule the work, as well as when you have specified in the timer. It will execute in the background even if the app is closed or backgrounded. If you didn't want your task to execute immediately you can use a PWR(PeriodicWorkRequest) with a FlexInterval. See the docs below for more info.
WorkManager Docs
WorkManager Architecture
WorkmManager CodeLab
For example, I created two PeriodicWorkRequests that refresh services and keeps the user logged in always by renewing their token. When the user authenticates the PeriodicWorkRequest is created. In my case, I didn't need it to fire right away as they have just received and cached this information so I utilized the FlexInterval. When the app is backgrounded or closed, the workers continue to refresh services every 12 hours and refresh the token every 6. It works like a charm.
Here is an example:
Build Work:
override fun beginWork() {
val periodicWorkRequest = PeriodicWorkRequest.Builder(
MyWorker::class.java,
REPEAT_INTERVAL, TimeUnit.MINUTES, // How often work should repeat
// Flex not required.
FLEX_INTERVAL, TimeUnit.MINUTES) // Limits execution into a time window
.setConstraints(
Constraints.Builder().setRequiredNetworkType(
NetworkType.CONNECTED).build())
.addTag(MY_WORKER_TAG)
.build()
WorkManager.getInstance().enqueueUniquePeriodicWork(
MY_UNIQUE_WORK,
ExistingPeriodicWorkPolicy.KEEP,
periodicLoginRequest)
Worker:
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
// DO WORK HERE
Result.success()
} else {
// HANDLE FAILURE HERE
Result.failure()
}
The above is a simple implementation, but it should give you the general idea.
I would like to use WorkManager to update the DB every 24 hours from midnight.
First, I'm understand that the Workmanager's PeriodicWorkRequest does not specify that the worker should operate at any given time.
So I used OneTimeWorkRequest() to give the delay and then put the PeriodicWorkRequest() in the queue, which runs every 24 hours.
1.Constraints
private fun getConstraints(): Constraints {
return Constraints.Builder()
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)
.build()
}
2.OneTimeWorkRequest
fun applyMidnightWorker() {
val onTimeDailyWorker = OneTimeWorkRequest
.Builder(MidnightWorker::class.java)
.setInitialDelay(getDelayTime(), TimeUnit.SECONDS)
.setConstraints(getConstraints())
.build()
val workerContinuation =
workManager.beginUniqueWork(Const.DAILY_WORKER_TAG,
ExistingWorkPolicy.KEEP,
onTimeDailyWorker)
workerContinuation.enqueue()
}
getDelayTime() is
private fun getDelayTime(): Long {
...
return midNightTime - System.currentTimeMillis()
}
3.MidnightWorker Class
class MidnightWorker : Worker() {
override fun doWork(): Result {
DailyWorkerUtil.applyDailyWorker()
return Worker.Result.SUCCESS
}
}
4.PeriodicWorkRequest
fun applyDailyWorker() {
val periodicWorkRequest = PeriodicWorkRequest
.Builder(DailyWorker::class.java, 24, TimeUnit.HOURS)
.addTag(Const.DAILY_WORKER)
.setConstraints(getConstraints()).build()
workManager.enqueue(periodicWorkRequest)
}
And I confirmed that the delayTime passed and the midnightWorker was running.
Of course, It worked normally without any relation to the network.
However, test results showed that the delay time worked regardless of device time. As if it were server time
This is my question.
1. No delay according to device time. I want to know if the delay works as per the server time standard.
2. I wonder if the PeriodicWorkRequest can provide InitialDelay like OneTimeWorkRequest.
you've used TimeUnit.SECONDS with setInitialDelay yet getDelayTime is working with milliseconds.
This maybe the cause of you problems