title: When does Android's WorkerManager stop a Worker?
body: <p>We have an Android app using WorkManager to handle background sync work. Our sync worker is like this:</p>
  
  <pre class="lang-java prettyprint-override"><code>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 &amp; 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 &gt; MAX_SYNC_RETRY_COUNT) {
              CBlogger.INSTANCE.log([...]);
              return true;
          } else {
              return false;
          }
      }
  
      @Override
      public void onStopped() {
          CBlogger.INSTANCE.log([...]);
      }
  }
  </code></pre>
  
  <p>The work is scheduled by a dedicate method of an helper class:</p>
  
  <pre class="lang-java prettyprint-override"><code>    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);
      }
  </code></pre>
  
  <p>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:</p>
  
  <pre class="lang-java prettyprint-override"><code>SyncWorkerManager.scheduleWorker(context, context.getResources().getString(R.string.sync_worker_policy_full), ExistingWorkPolicy.KEEP);
  </code></pre>
  
  <p>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.</p>
  
  <p>This strategy works all in all good, but sometimes we find in logs that Worker's <code>onStopped()</code> method is called a few seconds (about 10") after SyncWorker start. </p>
  
  <p>Known that we never programmatically stop a specific Worker for the outside and we only call <code>WorkManager.getInstance(context).cancelAllWork();</code> 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 <code>onStopped()</code> method?</p>
  
  <p>I know that it can happen when:</p>
  
  <ul>
  <li>Constraints are no longer satisfied (network connection dropped)</li>
  <li>Worker runs over 10' limit imposed by JobScheduler implementation (our scenario is tested on Android 9 device)</li>
  <li>New unique work enqueued with same name and REPLACE policy (we never use this policy in our app for the SyncWorker, only for PeriodicSyncWorker)</li>
  <li>Spurious calls due to <a href="https://issuetracker.google.com/issues/140055777" rel="noreferrer">this bug</a> (we work with "androidx.work:work-runtime:2.2.0")</li>
  </ul>
  
  <p>Is there any other condition that can cause Worker's to be stopped? Something like:</p>
  
  <ul>
  <li>Doze mode</li>
  <li>App stand-by buckets</li>
  <li>App background restrictions (Settings --> Apps --> My App --> Battery --> Allow Background)</li>
  <li>App battery optimization (Settings --> Apps --> My App --> Battery --> Battery Optimization)</li>
  </ul>
  
  <p>Thanks</p>