When does Android's WorkerManager stop a Worker?
Asked Answered
P

1

9

We have an Android app using WorkManager to handle background sync work. Our sync worker is like this:

public class SyncWorker extends Worker {

    [...]

    @NonNull
    @Override
    public Result doWork() {

        if (canNotRetry(getRunAttemptCount())) {
            // This could seem unreachable, consider removing... or not... because if stopped by the
            // system, the work might be retried by design
            CBlogger.INSTANCE.log([...]);
            return Result.success();
        }

        boolean syncOk = false;

        //Sync
        try (Realm realm = Realm.getDefaultInstance()) {

            // Doing sync related ops & network calls
            // checking this.isStopped() between operations to quit
            // sync activity when worker has to be stopped

            syncOk = true;
        } catch (Throwable throwable) {
            CBlogger.INSTANCE.log([...]);
        }

        // On error, continue with following code to avoid any logic in catch
        // This method must NOT throw any unhandled exception to avoid unique work to be marked as failed
        try {

            if (syncOk) {
                return Result.success();
            }

            if (canNotRetry(getRunAttemptCount() + 1)) {
                CBlogger.INSTANCE.log([...]);
                return Result.success();
            } else {
                CBlogger.INSTANCE.log([...]);
                return Result.retry();
            }
        } catch (Throwable e) {
            CBlogger.INSTANCE.log([...]);
            return Result.success();
        }
    }

    private boolean canNotRetry(int tryNumber) {
        // Check if the work has been retry too many times
        if (tryNumber > MAX_SYNC_RETRY_COUNT) {
            CBlogger.INSTANCE.log([...]);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public void onStopped() {
        CBlogger.INSTANCE.log([...]);
    }
}

The work is scheduled by a dedicate method of an helper class:

    public static void scheduleWorker(Context context, String syncPolicy, ExistingWorkPolicy existingWorkingPolicy){

        Constraints constraints = new Constraints.Builder()
                .setRequiresCharging(false)
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build();

        Data.Builder data = new Data.Builder();
        data.putString(context.getResources().getString(R.string.sync_worker_policy), syncPolicy);

        Log.d(TAG, "Scheduling one-time sync request");
        logger.info("Scheduling one-time sync request");
        OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder
                (SyncWorker.class)
                .setInputData(data.build())
                .setConstraints(constraints)
                .setBackoffCriteria(
                        BackoffPolicy.LINEAR,
                        OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                        TimeUnit.MILLISECONDS)
                .build();

        WorkManager.getInstance(context).enqueueUniqueWork("OneTimeSyncWorker", existingWorkingPolicy, oneTimeWorkRequest);
    }

that is called when user clicks on "Sync" button or by another worker that is scheduled to run every 20' and calls the helper's function this way:

SyncWorkerManager.scheduleWorker(context, context.getResources().getString(R.string.sync_worker_policy_full), ExistingWorkPolicy.KEEP);

so that a new sync is queued only if not already waiting or running. Notice that sync work policy enforces that a connected network is required.

This strategy works all in all good, but sometimes we find in logs that Worker's onStopped() method is called a few seconds (about 10") after SyncWorker start.

Known that we never programmatically stop a specific Worker for the outside and we only call WorkManager.getInstance(context).cancelAllWork(); during logout procedure or before a new login (that also schedules del periodic Worker), when does the system can decide to stop the worker and call its onStopped() method?

I know that it can happen when:

  • Constraints are no longer satisfied (network connection dropped)
  • Worker runs over 10' limit imposed by JobScheduler implementation (our scenario is tested on Android 9 device)
  • New unique work enqueued with same name and REPLACE policy (we never use this policy in our app for the SyncWorker, only for PeriodicSyncWorker)
  • Spurious calls due to this bug (we work with "androidx.work:work-runtime:2.2.0")

Is there any other condition that can cause Worker's to be stopped? Something like:

  • Doze mode
  • App stand-by buckets
  • App background restrictions (Settings --> Apps --> My App --> Battery --> Allow Background)
  • App battery optimization (Settings --> Apps --> My App --> Battery --> Battery Optimization)

Thanks

Proserpina answered 4/12, 2019 at 11:57 Comment(5)
I believe all 4 you mention at the end. Doze mode is very aggressive and will stop all work, network, etc. That being said, I'm wondering if this could happen if the user briefly looses internet access (or in poor network) and this causes Work Manager to consider "network is down" even if only for a second..Horick
as Martin Marconcini mentioned all reasons could be true, or your code might have thrown an error. Anyways, I've heard that in OnePlus devices there is a bug which kills apps on background (force stops them) which will need a restart of the app in order to start sync again.Privily
I agree, but imagine that Doze avoids the Worker to be started instead of stopping it suddenly once started. Might be that App Stand-By Buckets reduce Worker's time window?Proserpina
Well, without having tested this myself, I'd say that Doze mode has no clue whether work manager is running tasks or not. Doze decides it's time to doze and it acts. I hope Ian Lake or someone from Google sees this and comments :)Horick
Afaik WorkManager is designed to respect Dose mode, therefore I don't think the vise-versa works.Privily
V
0

There are multiple reasons a Worker can be stopped. You can explicitly ask for it to be cancelled or WorkManager might stop it for a variety of reasons which are documented here.

Viridescent answered 5/8, 2021 at 14:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.