Use Android Priority Job Queue library for your background tasks

Most applications have background tasks inside their code, existing a need to perform code in a no-UI thread. Using these tasks, we expect to keep the application responsive and robust, especially during unfavorable situations. But, can we have the best background tasks system ever?

Improving background tasks in Android

Android offers in its framework several alternatives to do them, e.g. AsyncTasks, Loaders and Services with a Thread Pool, but they have the following issues:

  • AsyncTasks are very coupled to UI. With just a device rotation, application can reset them.
  • Loaders are good for disk tasks but not for long network requests.
  • Services, although they are very decoupled to UI, the more services you are running at the same time, the more difficult will be concurrency and prioritization on it.

To solve these issues, we have Android Priority Job Queue library. This library is an implementation of a Job Queue specifically written for Android to easily schedule jobs (tasks) that run in the background, improving UX and application stability.

More advantages

In addition to the advantages described above, with this library you can:

  • Decouple application logic from your activities or fragments easily, making your code more robust and easy to refactor it and test it.
  • Prioritize background tasks, running firstly one jobs than others.
  • Run jobs only if you have internet connection, and detect network status changes so the queue launch these jobs when it returns.
  • Keep your job status in the same status after unexpected situations, e.g., an app crash, persisting it and restoring it as before.
  • Run jobs in parallel or group them in a serial execution.
  • Schedule tasks, delaying them after some time.
  • Integrate with other job scheduler such as JobScheduler or GCMNetworkManager.
  • etc.

starting - background tasks

Getting Started

First, you need to include the library in your project. It is available in Maven Central repository, so you can use it just adding the following line in the dependencies of your gradle script:

compile 'com.birbit:android-priority-jobqueue:2.0.0'

We highly recommend to use a event bus library with Priority Job Queue, e.g. Green Robot Event Bus.

compile 'org.greenrobot:eventbus:3.0.0'

Second, you need to configure your JobManager. There is an extended documentation of how to set up correctly in their wiki. Here you have an example,

Configuration.Builder builder = new Configuration.Builder(context)
    .minConsumerCount(1) // always keep at least one consumer alive
    .maxConsumerCount(3) // up to 3 consumers at a time
    .loadFactor(3) // 3 jobs per consumer
    .consumerKeepAlive(120) // wait 2 minute
    .customLogger(new CustomLogger() {
        private static final String TAG = "JOBS";
        @Override
        public boolean isDebugEnabled() {
            return true;
        }

        @Override
        public void d(String text, Object... args) {
            Log.d(TAG, String.format(text, args));
        }

        @Override
        public void e(Throwable t, String text, Object... args) {
            Log.e(TAG, String.format(text, args), t);
        }

        @Override
        public void e(String text, Object... args) {
            Log.e(TAG, String.format(text, args));
        }

        @Override
        public void v(String text, Object... args) {

        }
    });

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    builder.scheduler(FrameworkJobSchedulerService.createSchedulerFor(context,
            AppJobService.class), true);
} else {
    int enableGcm = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
    if (enableGcm == ConnectionResult.SUCCESS) {
        builder.scheduler(GcmJobSchedulerService.createSchedulerFor(context,
                AppGcmJobService.class), true);
    }
}
mJobManager = new JobManager(builder.build());

Third, you need to create your jobs according to your preferences. See how you can configure your individual jobs.

public FetchLocationByAddressJob(String address) {
    super(new Params(JobConstants.PRIORITY_NORMAL)
            .requireNetwork()
            .singleInstanceBy(TAG)
            .addTags(TAG)
    );

    ...
}

In addition to job constructor, you must override the following methods:

  • onAdded: Called when the job has been successfully added to the queue. It is a great place to communicate to UI  using an event the job will eventually run.
  • onRun: Background task logic goes here. For example, perform your network request.
  • shouldReRunOnThrowable: We can detect here if we want to rerun the job after an unfavorable situation or cancel it. For instance, imagine a request requires authentication, if the token is not valid you can cancel the current job and notify to app logic user must authenticate.
  • onCancel: Job has failed due to either it has exceeded the number of attempts or shouldReRunOnThrowable method cancelled.

Finally, create and add jobs to the queue, they will be run as soon as job requirements are meet.

public class FetchLocationByAddressJob extends Job {

    private String mAddress;

    public static final String TAG = FetchLocationByAddressJob.class.getCanonicalName();

    public FetchLocationByAddressJob(String address) {
        super(new Params(JobConstants.PRIORITY_NORMAL)
                .requireNetwork()
                .singleInstanceBy(TAG)
                .addTags(TAG)
        );

        mAddress = address;
    }

    @Override
    public void onAdded() {
        // Store address in database
        FakeDatabase.setLastAddress(getApplicationContext(), mAddress);
    }

    @Override
    public void onRun() throws Throwable {
        String address = FakeDatabase.getLastAddress(getApplicationContext());

        GeocodingInterface service = AppRetrofitManager.getGeocodingInterface();

        Call request = service.getInfoByAddress(address);
        Geocode geocode = AppRetrofitManager.performRequest(request);

        // Ensure we have information about the search address
        if (geocode == null || geocode.getResults().size() <= 0) {
            EventBus.getDefault().post(new LocationFailedEvent());
            return;
        }

        // Just retrieve the first result
        EventBus.getDefault().post(new LocationFetchedEvent(geocode.getResults().get(0)));
    }

    @Override
    protected void onCancel(int cancelReason, @Nullable Throwable throwable) {
        EventBus.getDefault().post(new LocationFailedEvent());
    }

    @Override protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, int runCount, int maxRunCount) {
        if (throwable instanceof ErrorRequestException) {
            ErrorRequestException error = (ErrorRequestException) throwable;
            int statusCode = error.getResponse().raw().code();
            if (statusCode >= 400 && statusCode < 500) {
                return RetryConstraint.CANCEL;
            }
        }

        return RetryConstraint.RETRY;
    }
}

If you want to see a full project using this type of background tasks with this library, check out this app.

Tip

Never work with Volley sync requests inside a job, use Retrofit ones instead.

After you run a lot of jobs, sometimes, you can realize that this type of background tasks that perform a Volley sync request are failing because of a strange error. No matter if you cancel all jobs and restart the queue when this happens, all jobs will fail from now on, even setting a timeout greater than 0, different to the default one.

Debugging you can see server returns successfully the response, but it fails when Volley checks the request status.

I think the reason why it is failing it is Volley caches requests, keeping a lock that makes the rest of jobs fails.

So, don’t use Volley with this library due to it will drive you mad.

 

Related articles

OCR on Android

Discovery of nearby bluetooth devices in Android

PDF reports in Android

 

16 thoughts on “Use Android Priority Job Queue library for your background tasks

  1. Hi,
    Thanks for tutorial its really helpful how you can make service call from job. i am looking for sample and all others have same twitter sdk example so I was looking example like this which helps me in this library implementation.
    Can you suggest me how i can data parse also on some job or using other thread ?

  2. So volley supports cache and retrofit doesn’t , so it can’t be used with job queue?

    ” I think the reason why it is failing it is Volley caches requests, keeping a lock that makes the rest of jobs fails.”

    What does it mean?

    Also, the calls are synchronous ?

    Thank you.

    • Both Volley and Retrofit support cache. I mean exactly the opposite what you comment. What I guess is Volley cache too much, it cache request object instead of request response. Then, if you launch the same request several times in a short period of time, sometimes, it started to fail with the following exception: timeout < 0. You should do all your requests synchronously using Priority Job Queue and a networking library in order to catch all exceptions and evaluate if you want to re run the job or not in shouldReRunOnThrowable method.

  3. Why do you declare onRun() with “throws Throwable”? The method doesn’t throw anything. If I use such code Android Studio complains about it as “Declaration redundancy”.

    • Hi,

      No, it is not possible to do so automatically. These jobs are thought for a single successful execution. If you want to schedule jobs like cron jobs, you need to do it by yourself, e.g. at the end of job, add another job with a delay.

      Best regards,

      José Carlos

  4. Hello There,
    Congratulation for good work.
    In my current application I am using SOCKET.IO for chatting purpose and as we know to keep the connection alive I am using a background service with ScheduledThreadPoolExecutor. Its is working all fine but with a cost of memory consumption.
    So to get rid of background service I am trying to use you lib. Every thing is good so far I am able to make the connection with Socket using job schaduler. But I am facing probelm when I manually clear app from opened apps tray. then I don’t get any notification from server.
    I am guessing job schaduler gets destroyed with it . Please let me know how to catch that event so that I can keep my connection alive for chatting.

    • Hi,

      Your guess is right. When you remove your app from recent apps, app is destroyed. The simplest & fastest way to fix it, I think it would be to have a background service just listening to this change and detect that event in the onTaskRemoved function of that background service.

Leave a Comment

Responsable » Solidgear.
Finalidad » Gestionar los comentarios.
Legitimación » Tu consentimiento.
Destinatarios » Los datos que me facilitas estarán ubicados en los servidores SolidgearGroup dentro de la UE.
Derechos » Podrás ejercer tus derechos, entre otros, a acceder, rectificar, limitar y suprimir tus datos.

By completing the form you agree to the Privacy Policy

This site uses Akismet to reduce spam. Learn how your comment data is processed.