0

I have a task to run several different jobs in Android app. Each job is long-running and cosumes network, database and file system much. Each job can be run manually by user or scheduled by AlarmManager. It is very important that each job runs till the end, so it needs to continue running after user leaves the app, or even when user does not open the app at all. Jobs have some ID attribute like this:

class Job {
    int id;
}

I need this hypothetical JobManager to receive jobs and sort them by ID. If a job with id = 1 is already running, then JobManager should skip all the subsequent jobs with id = 1 until this job is finished. But if a job is submitted with id = 2, then it is accepted and can be run in parallel with the first job.

The jobs should also to keep wake lock until completed, like it is done in CommonsWare's WakefulIntentService.

I have several ideas how to implement this, but all have their drawbacks:

  1. Subclass of the Service class that runs always in background and is automatically restarted, when killed for some reason. Drawbacks: it consumes resources even if not running anything, it is running on UI thread, so we have to manage some threads that can be killed by system as usual, each client has to start the Service and nobody knows, when to stop it.
  2. WakefulIntentService from CommonsWare. Drawbacks: because it is IntentService, it runs only sequentially, so it cannot check for existing running job.
  3. Boolean "running" flag in the database for each job. Check it every time we want to run a job. Drawbacks: too many requests to db, difficult to implement properly, sometimes 2 equal jobs still can run in parallel, not sure about flags staying "true" in case of any unexpected error.
  4. Existing library disigned for this purpose. As for now except CWAC-Wakeful I have found:

    but still I don't know, how to use these libraries to run exactly one centralized service, that whould accept jobs from any other Activity, Service, BroadcastReceiver, AlarmManager, etc, sort them by ID and run in parallel.

Please advise me what solution can be used in this case.

UPDATE: See below my own solution. I'm not sure, if it works in all possible cases. If you are aware of any problems that may arise with this, please comment.

afrish
  • 3,167
  • 4
  • 30
  • 38

3 Answers3

3

This seems to be suited for the new JobScheduler API on Lollipop, then you will have to make a wrapper around it to implement all the features that the sdk implementation is missing.

There is a compat library if you need to implement this on versions below Lollipop.

Julian Suarez
  • 4,499
  • 4
  • 24
  • 40
1

If anybody faces the same problem, here is the solution I came up with. I used Robospice lib, because it is the most robust way of running some jobs on a Service and syncing results back to the Activity. As I did not find any ways to use this lib with WakeLocks, I extended 2 classes: SpiceManager and SpiceRequest. The new classes, WakefulSpiceManager and WakefulSpiceRequest, actually borrow CommonsWare's ideas about WakeLocks, the implementation is very similar.

WakefulSpiceManager:

public class WakefulSpiceManager extends SpiceManager {
    private static final String NAME = "WakefulSpiceManager";
    private static volatile PowerManager.WakeLock wakeLock;
    private Context context;

    public WakefulSpiceManager(Context context, Class<? extends SpiceService> spiceServiceClass) {
        super(spiceServiceClass);
        this.context = context;
        start(context);
    }

    private static synchronized PowerManager.WakeLock getLock(Context context) {
        if (wakeLock == null) {
            PowerManager mgr = (PowerManager) context.getSystemService(Context.POWER_SERVICE);

            wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, NAME);
            wakeLock.setReferenceCounted(true);
        }

        return wakeLock;
    }

    public <T> void execute(WakefulSpiceRequest<T> request, RequestListener<T> requestListener) {
        PowerManager.WakeLock lock = getLock(context);
        lock.acquire();
        request.setLock(lock);

        // explicitly avoid caching
        super.execute(new CachedSpiceRequest<T>(request, null, ALWAYS_EXPIRED), requestListener);
    }
}

WakefulSpiceRequest:

public abstract class WakefulSpiceRequest<R> extends SpiceRequest<R> {
    private PowerManager.WakeLock lock;

    public WakefulSpiceRequest(Class<R> clazz) {
        super(clazz);
    }

    public void setLock(PowerManager.WakeLock lock) {
        this.lock = lock;
    }

    @Override
    public final R loadDataFromNetwork() throws Exception {
        try {
            return execute();
        } finally {
            if (lock.isHeld()) {
                lock.release();
            }
        }
    }

    public abstract R execute() throws Exception;
}

So basically here we acquire the lock every time we are going to send a request from WakefulSpiceManager. After that the lock is passed to the WakefulSpiceRequest. When request finishes its work, it cleans the lock with release() method - this will happen even if the activity with WakefulSpiceManager is already destroyed.

Now we use those classes in usual Robospice's manner, with the only exception that we need to pass only WakefulSpiceRequests to execute on WakefulSpiceManager:

    WakefulSpiceManager manager = new WakefulSpiceManager(context, MyService.class);
    manager.execute(new WakefulSpiceRequest<MyResult>(MyResult.class) {
        @Override
        public MyResult execute() throws Exception {
            return ...
        }
    }, new RequestListener<MyResult>() {
        @Override
        public void onRequestFailure(SpiceException e) {
            ...
        }

        @Override
        public void onRequestSuccess(MyResult result) {
            ...
        }
    });
afrish
  • 3,167
  • 4
  • 30
  • 38
-1

The new Workmanager will help you schedule tasks in any order you want. You can easily set constraints to the job that you want to be en-queued along with many other advantages over JobScheduler API or alarm manager. Have a look at this video for a brief intro - https://www.youtube.com/watch?v=pErTyQpA390 (WorkManager at 21:44).

EDIT: Updated my ans to show the capabilities of the new API You will not need ids to handle the jobs with this one. You can simply enqueue the task and the rest will be handled by the API itself.

Some work case scenarios are

  • WorkManager.getInstance() .beginWith(workA) // Note: WorkManager.beginWith() returns a // WorkContinuation object; the following calls are // to WorkContinuation methods .then(workB) .then(workC) .enqueue();

  • WorkManager.getInstance() // First, run all the A tasks (in parallel): .beginWith(workA1, workA2, workA3) // ...when all A tasks are finished, run the single B task: .then(workB) // ...then run the C tasks (in any order): .then(workC1, workC2) .enqueue();

Annsh Singh
  • 435
  • 5
  • 12
  • 1
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/19686953) – mlinth May 10 '18 at 07:48
  • @mlinth Thanks for the review. Let me add on to my answer to improve it. – Annsh Singh May 10 '18 at 08:12