Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unique OneTimeWorkRequest in Workmanager

We are using OneTimeWorkRequest to start background task in our project.

  1. At application start, we are starting the OneTimeWorkRequest (say req A)
  2. Depends on user's action we start the same work request A.

At some cases, if the app gets killed when the work request A is in progress, Android automatically restarts the request A when the app restarts. Once again we are also starting the request A again. So two instances of the request A runs in parallel and leads to a deadlock.

To avoid this, I did below code in app start to check if the worker is running but this always returns false.

public static boolean isMyWorkerRunning(String tag) {
        List<WorkStatus> status = WorkManager.getInstance().getStatusesByTag(tag).getValue();
        return status != null;
    }

Is there a better way to handle this?

I checked the beginUniqueWork(). Is it costlier if I have only one request?

Edit 2: This question is about unique One time task. For starting unique Periodic task we had a separate API enqueueUniquePeriodicWork(). But we did not have an API for starting unique onetime work. I was confused to use between continuation object or manually check and start approach.

In recent build they Android added new api for this enqueueUniqueWork(). This is the exact reason they mentioned in their release notes.

Add WorkManager.enqueueUniqueWork() API to enqueue unique OneTimeWorkRequests without having to create a WorkContinuation. https://developer.android.com/jetpack/docs/release-notes

like image 470
Aram Avatar asked Aug 07 '18 10:08

Aram


People also ask

Which existing policy is available in WorkManager?

WorkManager PoliciesKEEP — keeps the existing unfinished WorkRequest. Enqueues it if one does not already exist. REPLACE — always replace the WorkRequest. Cancels and deletes the old one, if it exists.

Is WorkManager deprecated?

This method is deprecated. Call getInstance instead.

Can WorkManager be used to repeat jobs?

Retry and backoff policyIf you require that WorkManager retry your work, you can return Result. retry() from your worker. Your work is then rescheduled according to a backoff delay and backoff policy. Backoff delay specifies the minimum amount of time to wait before retrying your work after the first attempt.

What is WorkManager used for?

WorkManager is intended for work that is required to run reliably even if the user navigates off a screen, the app exits, or the device restarts. For example: Sending logs or analytics to backend services. Periodically syncing application data with a server.


2 Answers

Edit 2:

Nov 8th release notes:

https://developer.android.com/jetpack/docs/release-notes

Add WorkManager.enqueueUniqueWork() API to enqueue unique OneTimeWorkRequests without having to create a WorkContinuation.

This says, alpha11 has this new API to uniquely enqueue a onetimework.

I tried changing the code as follows:

OneTimeWorkRequest impWork = new OneTimeWorkRequest.Builder(WorkerNotesAttachment.class)
            .addTag(RWORK_TAG_NOTES)
            .build();
WorkManager.getInstance().enqueueUniqueWork(RWORK_TAG_NOTES, ExistingWorkPolicy.REPLACE, impWork);

I tried using the beginUniqueWork API. But it fails to run sometimes. So I ended up writing the following function.

public static boolean isMyWorkerRunning(String tag) {
    List<WorkStatus> status = null;
    try {
        status = WorkManager.getInstance().getStatusesByTag(tag).get();
        boolean running = false;
        for (WorkStatus workStatus : status) {
            if (workStatus.getState() == State.RUNNING
                    || workStatus.getState() == State.ENQUEUED) {
                return true;
            }
        }
        return false;

    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    return false;
}

We need to get all the WorkStatus objects and check if atleast one of them is in running or Enqueued state. As the system keeps all the completed works in the DB for few days (Refer pruneWork()), we need to check all the work instances.

Invoke this function before starting the OneTimeWorkRequest.

public static void startCacheWorker() {

    String tag = RWORK_TAG_CACHE;

    if (isMyWorkerRunning(tag)) {
        log("worker", "RWORK: tag already scheduled, skipping " + tag);
        return;
    }
    // Import contact for given network
    OneTimeWorkRequest impWork = new OneTimeWorkRequest.Builder(WorkerCache.class)
            .addTag(tag)
            .build();
    WorkManager.getInstance().enqueue(impWork);
}
like image 135
Aram Avatar answered Sep 19 '22 12:09

Aram


You can use beginUniqueWork() with a unique name.
If you use ExistingWorkPolicy:
APPEND: the 2 requests will run serial.
KEEP: will not run the second request if the first is running.
REPLACE: the 2 requests will run parallel.

like image 35
NickF Avatar answered Sep 18 '22 12:09

NickF