Search code examples
javaandroidandroid-serviceandroidx

AndroidX Work Library is canceling operations, seemingly without reason


I am working with the AndroidX Work Dependency to try and run some background service operations. I am currently running the most recent stable version, 2.2.0 as of posting this question.

The actual operation I am running in the background is a fairly heavy-CPU operation as it is using some compression code in one of my libraries (here) and can take anywhere from 3-30 minutes depending on the size and length of the video in question.

Here is the code I have that builds and runs the work request:

    public static void startService(){
        //Create the Work Request
        String uniqueTag = "Tag_" + new Date().getTime() + "_ChainVids";
        OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(CompleteVideoBackgroundService.class);
        Constraints.Builder constraints = new Constraints.Builder();
        constraints.setRequiredNetworkType(NetworkType.CONNECTED);
        builder.setConstraints(constraints.build());
        builder.setInitialDelay(1, TimeUnit.MILLISECONDS);
        builder.addTag(uniqueTag);
        Data inputData = new Data.Builder().putString("some_key", mySerializedJSONString).build();
        builder.setInputData(inputData);
        OneTimeWorkRequest compressRequest = builder.build();

        //Set the Work Request to run
        WorkManager.getInstance(MyApplication.getContext())
                .beginWith(compressRequest)
                .enqueue();
    }

It then fires off this class which runs all of the background service operations:

public class MyServiceSampleForStackoverflow  extends Worker {

    private Context context;
    private WorkerParameters params;

    public MyServiceSampleForStackoverflow(@NonNull Context context, @NonNull WorkerParameters params){
        super(context, params);
        this.context = context;
        this.params = params;
    }

    /**
     * Trimming this code down considerably, but the gist is still here
     */
    @NonNull
    @Override
    public Result doWork() {
        try {
            //Using using a hard-coded 50% for this SO sample
            float percentToBringDownTo = 0.5F;
            Uri videoUriToCompress = MyCustomCode.getVideoUriToCompress();
            VideoConversionProgressListener listener = (progressPercentage, estimatedNumberOfMillisecondsLeft) -> {
                float percentComplete = (100 * progressPercentage);
                //Using this value to update the Notification Bar as well as printing in the logcat. Erroneous code removed
            };
            String newFilePath = MyCustomCode.generateNewFilePath();
            //The line below this is the one that takes a while as it is running a long operation
            String compressedFilePath = SiliCompressor.with(MyApplication.getContext()).compressVideo(
                    listener, videoUriToCompress.getPath(), newFilePath, percentToBringDownTo);
            //Do stuff here with the compressedFilePath as it is now done
            return Result.success();
        } catch (Exception e){
            e.printStackTrace();
            return Result.failure();
        }
    }
}

Once in a while, without any rhyme or reason to it, the worker randomly stops without me telling it to do so. When that happens, I am seeing this error:


Work [ id=254ae962-114e-4088-86ec-93a3484f948d, tags={ Tag_1571246190190_ChainVids, myapp.packagename.services.MyServiceSampleForStackoverflow } ] was cancelled
    java.util.concurrent.CancellationException: Task was cancelled.
        at androidx.work.impl.utils.futures.AbstractFuture.cancellationExceptionWithCause(AbstractFuture.java:1184)
        at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:514)
        at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
        at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:284)
        at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)

I am quite literally just staring at my phone with and not interacting with it at all when this randomly happens. I am not trying to run other apps, nor am I trying to hog the CPU for something else. I never see any stacktrace of my own and no immediate problem or cause is visible.

The question therefore is, what is happening here that is randomly stopping the Worker service without any provocation? Why is it randomly stopping operations?

Thanks all.

EDIT 1

  • I did test removing the network constraints requirement line thinking that may be causing the issue and I did see it occur even after that, so I don't believe that is the problem.

  • I am testing on a Google Pixel 3, API 28, Android 9, but any other device I have tested regardless of API level (minimum supported is 21) has shown the same issue.

Edit 2

  • I tried rewriting the class to work on the Asynchronous approach by having the class extend ListenableWorker instead of Worker and that did not resolve the issue.

Solution

  • you are most likely facing one of 2 issues.

    First, assuming your background service runs more than 10 minutes can take anywhere from 3-30 minutes depending on the size and length of the video in question, you may be running into a hard time limit which is imposed by the WorkManager code.

    In the docs here they say: The system instructed your app to stop your work for some reason. This can happen if you exceed the execution deadline of 10 minutes.

    That seems the most likely, but the other issue could be related to Android background limitations outlined here in which it details changes related to Apps that target Android 8.0 or higher. As you mentioned in an edit, you are testing on a Google Pixel 3, API 28, Android 9 and that may be directly related.

    As far as solutions go, the simplest, albeit a frustrating solution, would be to tell the user that they need to keep the app in the foreground. This would at least prevent that 10 minute gap.

    Another option would be to utilize the new Bubble API that was introduced in API 29. The docs are here and the section of docs that might interest you is where it says, When a bubble is expanded, the content activity goes through the normal process lifecycle, resulting in the application becoming a foreground process. Making a miniaturized 'expanded' view and having that be expanded by users when the app closes my be a good alternative solution to bypassing that 10 minute timer.