Менеджер по работе в Android

У меня есть задача запустить несколько разных заданий в приложении для Android. Каждое задание является длительным и сильно потребляет сеть, базу данных и файловую систему. Каждое задание может запускаться пользователем вручную или по расписанию с помощью AlarmManager. Очень важно, чтобы каждое задание выполнялось до конца, поэтому оно должно продолжать выполняться после того, как пользователь покинет приложение или даже когда пользователь вообще не открывает приложение. Задания имеют некоторый атрибут ID, например:

class Job {
    int id;
}

Мне нужен этот гипотетический JobManager, чтобы получать задания и сортировать их по ID. Если задание с id = 1 уже выполняется, то JobManager должен пропустить все последующие задания с id = 1, пока это задание не будет завершено. Но если задание отправляется с id = 2, то оно принимается и может выполняться параллельно с первым заданием.

Задания также должны сохранять блокировку пробуждения до завершения, как это делается в WakefulIntentService CommonsWare.

У меня есть несколько идей, как это реализовать, но у всех есть свои недостатки:

  1. Подкласс класса Service, который всегда работает в фоновом режиме и автоматически перезапускается, если по какой-либо причине его уничтожают. Недостатки: он потребляет ресурсы, даже если ничего не запускает, он работает в потоке пользовательского интерфейса, поэтому нам приходится управлять некоторыми потоками, которые могут быть уничтожены системой, как обычно, каждый клиент должен запускать Сервис, и никто не знает, когда его остановить.
  2. WakefulIntentService от CommonsWare. Недостатки: поскольку это IntentService, он запускается только последовательно, поэтому не может проверить наличие уже запущенного задания.
  3. Булев флаг «работает» в базе данных для каждого задания. Проверяйте его каждый раз, когда мы хотим запустить задание. Недостатки: слишком много запросов к БД, сложно реализовать правильно, иногда 2 одинаковых задания все еще могут выполняться параллельно, не уверен, что флаги останутся «истинными» в случае какой-либо неожиданной ошибки.
  4. Существующая библиотека предназначена для этой цели. На данный момент кроме CWAC-Wakeful я нашел:

    но все же я не знаю, как использовать эти библиотеки для запуска ровно одной централизованной службы, которая будет принимать задания от любых других действий, служб, BroadcastReceiver, AlarmManager и т. д., сортировать их по идентификатору и запускать параллельно.

Посоветуйте, какое решение можно использовать в этом случае.

ОБНОВЛЕНИЕ: см. ниже мое собственное решение. Я не уверен, работает ли это во всех возможных случаях. Если вы знаете о каких-либо проблемах, которые могут возникнуть с этим, пожалуйста, прокомментируйте.


person afrish    schedule 15.01.2015    source источник


Ответы (3)


Кажется, это подходит для нового API JobScheduler на Lollipop, тогда вам придется сделать обертку вокруг него, чтобы реализовать все функции, которых нет в реализации sdk.

Существует библиотека compat, если вам нужно реализовать это в версиях ниже Lollipop.

person Julian Suarez    schedule 15.01.2015

Если кто-то сталкивался с такой же проблемой, вот решение, которое я придумал. Я использовал библиотеку Robospice, потому что это самый надежный способ запуска некоторых заданий в службе и синхронизации результатов обратно в действие. Поскольку я не нашел способов использовать эту библиотеку с WakeLocks, я расширил 2 класса: SpiceManager и SpiceRequest. Новые классы, WakefulSpiceManager и WakefulSpiceRequest, фактически заимствуют идеи CommonsWare о WakeLocks, реализация очень похожа.

Уэйфулспайсменеджер:

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;
}

Таким образом, здесь мы получаем блокировку каждый раз, когда собираемся отправить запрос от WakefulSpiceManager. После этого блокировка передается WakefulSpiceRequest. Когда запрос завершает свою работу, он снимает блокировку методом release() — это произойдет, даже если активность с WakefulSpiceManager уже уничтожена.

Теперь мы используем эти классы в обычной манере Robospice, за тем единственным исключением, что нам нужно передать только WakefulSpiceRequests для выполнения в 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) {
            ...
        }
    });
person afrish    schedule 16.01.2015

Новый Workmanager поможет вам планировать задачи в любом порядке. Вы можете легко установить ограничения для задания, которое вы хотите поставить в очередь, наряду со многими другими преимуществами по сравнению с JobScheduler API или диспетчером тревог. Взгляните на это видео для краткого введения - https://www.youtube.com/watch?v=pErTyQpA390 (WorkManager в 21:44).

РЕДАКТИРОВАТЬ: Обновлен мой ответ, чтобы показать возможности нового API. Вам не понадобятся идентификаторы для обработки заданий с этим. Вы можете просто поставить задачу в очередь, а остальное сделает сам API.

Некоторые сценарии работы

  • 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();

person Annsh Singh    schedule 10.05.2018
comment
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если связанная страница изменится. – Из обзора - person mlinth; 10.05.2018
comment
@mlinth Спасибо за обзор. Позвольте мне добавить к моему ответу, чтобы улучшить его. - person Annsh Singh; 10.05.2018