Android WorkManager повторно отправляет уникальную задачу во время ее работы

У меня есть приложение для Android, которому необходимо выполнять длительную обработку изображений в фоновом режиме (обработка многих его фрагментов)

  • Я использовал WorkManager для выполнения задачи
  • Я использую уведомление пользователя, чтобы сообщить о прогрессе

Версии

  • Уровень API 28
  • Телефон под управлением Oreo
  • androidx.work:work-runtime:2.1.0

WorkManager настроен следующим образом (упрощенный код)

@NonNull
@Override
public Result doWork() {
    // 1 Create a NotificationChannel with unique ID
    // 2 read params from getInputData
    // 3 create as many instances of a class 
    //     that extends Callable<String> as there are small subtasks
    // 4 add all these tasks to a ThreadPoolExecutor and call
    //     invokeAll() which blocks the main Worker thread as required
    //     by the WorkManager API
    // 5 Everytime a subtask finishes, update the notification progress
}

@Override
public void onStopped(){
    // Call .shutDown() on the ThreadPoolExecutor
}

Сама работа представляется со следующими параметрами

WorkManager mWorkManager = WorkManager.getInstance(context);
Data input = new Data.Builder()
                 .putString(PARAM_IN_DTO, dto.toString())
                 .build();
OneTimeWorkRequest mRequest = new OneTimeWorkRequest
                 .Builder(ExtractorWorker.class)
                 .setInputData(input)
                 .addTag(dto.mapName)
                 .build();
mWorkManager.enqueueUniqueWork(
                 WORK_NAME,
                 ExistingWorkPolicy.KEEP,
                 mRequest);

Поскольку WORK_NAME уникален с приведенным выше кодом, я ожидаю, что две задачи никогда не будут выполняться одновременно (обратите внимание, что я пробовал с ExistingWorkPolicy.REPLACE - та же проблема)

Задание запускается правильно, и прогресс в уведомлении начинает обновляться. Через некоторое время кажется, что задание запускается снова, а предыдущее все еще выполняется. Я вижу это по уникальному уведомлению, переключающемуся между двумя состояниями (возраст уведомления между now и предыдущим возрастом, а также прогресс между 0% и предыдущим прогрессом). Теперь явно есть две задачи, которые обновляют одно и то же уведомление. Я подтвердил это, установив случайный идентификатор уведомления вместо уникального, и появилось два уведомления, каждое из которых живет своей жизнью.

Я думаю

  • Почему WorkManager снова отправляет эту задачу, в то время как предыдущая все еще выполняется, и я ничего не запрашивал из приложения
  • Как могут выполняться два задания одновременно, если я отправляю только одно с enqueueUniqueWork

person tishu    schedule 10.08.2019    source источник


Ответы (2)


Сколько времени может занять «длительная обработка изображений»?

WorkManager имеет жесткое ограничение в 10 минут для своих рабочих процессов (такое же ограничение базового API JobScheduler). Если Worker занимает более 10 минут, он отменяется и переносится, все, что возвращается Worker в этом случае, выбрасывается WorkManager. Рабочий процесс будет выполнен так, как если бы он никогда не выполнялся раньше.

Если вам не удастся остановить Worker (есть также функция isStopped(), которую вы можете использовать в своем методе doWork() для проверки этого условия), вы можете получить два экземпляра вашего worker, работающих одновременно.

Дополнительную информацию можно найти в документации

person pfmaggi    schedule 10.08.2019

Думаю, теперь я понимаю поведение. Когда WorkManager хочет прервать задание (чтобы возобновить его позже), onStopped() вызывается с очень коротким временем ожидания. Вызов executor.shutDown() превышает это, и выдается TimeoutException, что, по-видимому, приводит к тому, что завершение работы не выполняется нормально. Казнь продолжается.

По истечении этого тайм-аута и из-за того, что doWork ничего не вернул вовремя, WorkManager должен предположить, что значение результата равно retry или fail, и повторно отправляет задание своему внутреннему исполнителю.

Короче говоря, нет никакой гарантии, что два задания не будут выполняться параллельно. Если задание не может быть правильно и быстро остановлено при вызове onStopped, тем не менее будет отправлено новое задание. Workmanager не управляет потоками в своем исполнителе напрямую и не прерывает задания, которые не останавливаются должным образом при необходимости и могут перезапустить его.

Решением в этом случае было установить внутреннее состояние моих заданий на отмену, чтобы они заканчивались сразу после запуска. После того, как исполнитель исчерпал себя, он удаляет файл блокировки. Новый процесс (возобновленный) ожидает исчезновения файла блокировки перед проверкой состояния работы и возобновляется оттуда.

person tishu    schedule 10.08.2019