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

2

8

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.

Sunstroke answered 31/1, 2019 at 8:38 Comment(12)
Post the code on how you create the recurring job.Belaud
@Belaud added the code.Sunstroke
Are you sure you are not hitting this if (lastWorkId != null) { //cancel pending work } too early? Maybe you have a problem somewhere and you are continuosly deleting your work before it is executed. What's strange is that you are getting this only on 2 devices of 5 :/Belaud
Yes but whenever I cancel all work, I go ahead and schedule it again.Sunstroke
on what device you check it? i can say for sure that WorkManager will not work properly on Xiaomi devices because of the MIUI optimization. so when the app get killed the system destroy all tasks. this is super annoying. you can see list of manufacturers that decided to make whatever they want hereKraemer
@PrasadPawar But if you cancel it before the 15 minutes needed to run, it will never be executed.Belaud
@Belaud So mostly I've seen that worker was triggered immediately when scheduled. Does this mean that first worker trigger can be anywhere between 0-15 minutes?Sunstroke
@MaksimNovikov Right. but i'm testing on Mi A2 as well which doesn't have these optimizations. Its an Android One device.Sunstroke
@PrasadPawar Yes, the scheduling time is not fixed, it's an indication. By the way read here: issuetracker.google.com/issues/113676489 don't know if this is your caseBelaud
@Belaud I have this issue on Android One device as well. So I don't think this is the 'chinese rom battery saver' issue.Sunstroke
@Belaud Regarding the scheduling, I've tried waiting for 15+ minutes without the call to my scheduleWork() with my app in foreground and device screen on! Still worker not triggered :(Sunstroke
@PrasadPawar maybe it's better if you create an issue on the work manager issuetracker, it could be a bug in the library. Just post all the information you have about that and on which combination of device-android version you are getting the problemBelaud
S
12

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:

  1. Remove all code to schedule your workers.

  2. 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.

  3. 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.

Sunstroke answered 31/1, 2019 at 20:1 Comment(0)
C
1

You're doing a few things that are incorrect.

  1. 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

  2. If you only want one particular copy of a type of work, you should use enqueueUniqueWork instead of enqueue.

  3. 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()

Crystallize answered 31/1, 2019 at 23:47 Comment(1)
The last 5 lines of code in both the methods are useless I agree. But you'll need to use pruneWork() or else workers won't be triggered on some devices. Better to be safe than sorry!Sunstroke

© 2022 - 2024 — McMap. All rights reserved.