Планировщик заданий Android

В этом руководстве описано, как спланировать фоновую работу с помощью API планировщика заданий Android, который доступен на устройствах Android под управлением ОС Android 5.0 (уровень API 21) и более поздних версий.

Обзор

Один из лучших способов поддерживать хорошую отзывчивость приложения Android — выполнять сложную или длительную работу в фоновом режиме. При этом важно, чтобы фоновая работа не влияла негативно на взаимодействие пользователя с устройством.

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

Android предоставляет приведенные ниже API для выполнения работы в фоновом режиме, но их недостаточно для интеллектуального планирования заданий.

  • Службы намерений — службы намерений отлично подходят для выполнения работы, однако они не предоставляют никаких способов планирования работы.
  • AlarmManager — эти API позволяют планировать работу только, но не предоставляют возможности фактически выполнять работу. Кроме того, AlarmManager поддерживает только ограничения на основе времени, то есть создает оповещение в определенное время или по истечении определенного времени.
  • Широковещательные приемники — приложение Android может настроить широковещательные приемники для выполнения работы в ответ на системные события или намерения. При этом широковещательные приемники не позволяют управлять временем выполнения заданий. Кроме того, изменения в операционной системе Android в будущем ограничат возможность выполнять широковещательные приемники, а также типы работы, на которую они могут реагировать.

Существуют два ключевых аспекта эффективного выполнения фоновой работы (иногда называемой фоновым заданием или просто заданием):

  1. Интеллектуальное планирование работы — важно, чтобы, когда приложение выполняет работу в фоновом режиме, что делает это как хороший гражданин. В идеале приложение не должно требовать выполнения задания. Вместо этого оно должно указать условия, при которых задание может быть выполнено, а затем запланировать в операционной системе задание, которое выполнит нужную работу при выполнении этих условий. Это позволяет Android самостоятельно управлять заданием, повышая эффективность устройства. Например, сетевые запросы могут собираться в пакет и выполняться одновременно, чтобы оптимизировать затраты на работу с сетью.
  2. Инкапсулируя работу. Код для выполнения фоновой работы должен быть инкапсулирован в дискретном компоненте, который может выполняться независимо от пользовательского интерфейса и будет относительно легко перепланировать работу, если работа не завершится по какой-то причине.

Планировщик заданий Android — это платформа, встроенная в операционную систему Android, которая предоставляет гибкий API для удобного планирования фоновой работы. Планировщик заданий Android состоит из следующих типов:

  • системная служба Android.App.Job.JobScheduler, которая используется для планирования, выполнения и (при необходимости) отмены заданий от имени приложения Android;
  • абстрактный класс Android.App.Job.JobService, который должен быть дополнен логикой для выполнения в фоновом задании. Это означает, что JobService отвечает за асинхронное выполнение работы;
  • объект Android.App.Job.JobInfo, который содержит критерии, используемые Android при выборе времени для запуска задания.

Чтобы запланировать работу с помощью диспетчера заданий Android, приложение Xamarin.Android должно инкапсулировать код в классе, который расширяет класс JobService. JobService содержит три метода жизненного цикла, которые могут быть вызваны в течение времени существования задания.

  • bool OnStartJob(JobParameters parameters) — этот метод вызывается JobScheduler для выполнения работы и выполняется в основном потоке приложения. Именно JobService отвечает за асинхронное выполнение работы и возвращает true, если еще осталась невыполненная работа, и false по завершении этой работы.

    Когда JobScheduler вызывает этот метод, он запрашивает и сохраняет блокировку в режиме бодрствования для Android на время выполнения задания. По завершении задания JobService должен сообщить об этом в JobScheduler, вызвав метод JobFinished (см. далее).

  • JobFinished(Параметры JobParameters, bool needsReschedule) — этот метод должен вызываться методом JobService , чтобы сообщить, JobScheduler что работа выполнена. Если JobFinished не вызывается, JobScheduler не удалит блокировку в режиме бодрствования, что приведет к быстрой разрядке аккумулятора.

  • bool OnStopJob (параметры JobParameters) — это вызывается, когда задание преждевременно остановлено Android. Он должен возвращать true, если нужно запланировать задание повторно в соответствии с критериями повтора (подробнее описано ниже).

Есть возможность указать ограничения или триггеры, которые будут контролировать время выполнения задания. Например, можно ограничить задание так, чтобы оно выполнялось только во время зарядки устройства, или запускать задание при съемке фото.

В этом руководство подробно описано, как реализовать класс JobService и запланировать его выполнение в JobScheduler.

Требования

Для планировщика заданий Android требуется Android с уровнем API 21 (Android 5.0) или более поздней версии.

Использование планировщика заданий Android

Использование API JobScheduler для Android состоит из трех этапов:

  1. Реализуйте тип JobService и включите в него нужную работу.
  2. Используйте объект JobInfo.Builder, чтобы создать объект JobInfo, который будет содержать критерии, по которым JobScheduler выполнит задание.
  3. Отправьте это задание с помощью JobScheduler.Schedule.

Реализация JobService

Вся работа, выполняемая планировщиком заданий Android, должна быть выполнена в типе, расширяющем абстрактный класс Android.App.Job.JobService. Создание JobService очень похоже на создание Service с помощью платформы Android.

  1. Расширьте класс JobService.
  2. Включите в подкласс ServiceAttribute и задайте параметру Name строковое значение, которое состоит из имени пакета и имени класса (см. следующий пример).
  3. Для свойства Permission элемента ServiceAttribute укажите строковое значение android.permission.BIND_JOB_SERVICE.
  4. Переопределите метод OnStartJob, добавив код для выполнения работы. Android будет вызывать этот метод в основном потоке приложения, чтобы выполнить задание. Если работа требует больше времени, чем несколько миллисекунд, ее нужно выполнять в отдельном потоке, чтобы избежать блокировки приложения.
  5. По завершении работы JobService должен вызвать метод JobFinished. С помощью этого метода JobService сообщает JobScheduler о том, что работа выполнена. Отсутствие вызова JobFinished приведет к тому, что JobService сохранит уже ненужные требования к устройству и сократит время его автономной работы.
  6. Мы также рекомендуем переопределить метод OnStopJob. Этот метод вызывается Android, если нужно остановить задание до его выполнения, что позволяет JobService корректно освободить ресурсы. Этот метод должен возвращать true, если нужно заново запланировать задание, или false, если повторное выполнение не требуется.

Следующий код является примером простейшего JobService для приложения, использующего TPL для асинхронного выполнения некоторой работы.

[Service(Name = "com.xamarin.samples.downloadscheduler.DownloadJob", 
         Permission = "android.permission.BIND_JOB_SERVICE")]
public class DownloadJob : JobService
{
    public override bool OnStartJob(JobParameters jobParams)
    {            
        Task.Run(() =>
        {
            // Work is happening asynchronously
                      
            // Have to tell the JobScheduler the work is done. 
            JobFinished(jobParams, false);
        });

        // Return true because of the asynchronous work
        return true;  
    }

    public override bool OnStopJob(JobParameters jobParams)
    {
        // we don't want to reschedule the job if it is stopped or cancelled.
        return false; 
    }
}

Создание JobInfo для планирования задания

Приложения Xamarin.Android не создают экземпляр JobService напрямую, а вместо этого передают объект JobInfo в JobScheduler. JobScheduler создаст экземпляр запрошенного объекта JobService, чтобы запланировать и запустить JobService в соответствии с метаданными в JobInfo. Объект JobInfo должен содержать следующие сведения:

  • JobId — это int значение, используемое для идентификации задания JobScheduler. Повторное использование этого значения приведет к обновлению существующего задания. Это значение должно быть уникальным для каждого приложения.
  • JobService — этот параметр является ComponentName явным образом идентифицирует тип, который JobScheduler должен использоваться для запуска задания.

Этот метод расширения демонстрирует создание JobInfo.Builder в Android с помощью объекта Context, например действия:

public static class JobSchedulerHelpers
{
    public static JobInfo.Builder CreateJobBuilderUsingJobId<T>(this Context context, int jobId) where T:JobService
    {
        var javaClass = Java.Lang.Class.FromType(typeof(T));
        var componentName = new ComponentName(context, javaClass);
        return new JobInfo.Builder(jobId, componentName);
    }
}

// Sample usage - creates a JobBuilder for a DownloadJob and sets the Job ID to 1.
var jobBuilder = this.CreateJobBuilderUsingJobId<DownloadJob>(1);

var jobInfo = jobBuilder.Build();  // creates a JobInfo object.

У планировщика заданий Android есть мощная возможность управлять временем выполнения или условиями, при которых может выполняться задание. В следующей таблице описаны некоторые методы JobInfo.Builder, которые позволяют приложению влиять на выполнение задания.

Метод Description
SetMinimumLatency Указывает задержку (в миллисекундах), которая должна соблюдаться перед выполнением задания.
SetOverridingDeadline Объявляет, что задание должно быть завершено до истечения указанного времени (в миллисекундах).
SetRequiredNetworkType Указывает требования к сетевому подключению для выполнения задания.
SetRequiresBatteryNotLow Задание может выполняться, только если устройство не отображает пользователю предупреждение о низком уровне заряда.
SetRequiresCharging Задание может выполняться только во время зарядки аккумулятора.
SetDeviceIdle Задание запускается только во время работы устройства.
SetPeriodic Задание должно выполняться регулярно.
SetPersisted Задание должно сохраняться даже после перезагрузки устройства.

SetBackoffCriteria предоставляет некоторые рекомендации о том, как долго JobScheduler будет ожидать перед повторной попыткой выполнить задание. Критерий откладывания разделяется на две части: задержка в миллисекундах (по умолчанию 30 секунд) и используемый тип откладывания (иногда называется политикой откладывания или политикой повтора). В перечислении Android.App.Job.BackoffPolicy инкапсулируются две политики:

  • BackoffPolicy.Exponential — Экспоненциальная политика обратной передачи увеличит начальное значение обратной передачи экспоненциально после каждого сбоя. После первой неудачной попытки библиотека выдерживает паузу, длительность которой определяется заданным начальным периодом (например, 30 секунд), а затем повторно назначает задание. При второй неудаче библиотека будет ожидать не менее 60 секунд перед попыткой повторного запуска. После третьей неудачной попытки библиотека будет ждать 120 секунд и т. д. Это значение по умолчанию.
  • BackoffPolicy.Linear — Эта стратегия представляет собой линейную обратную передачу, которую задание должно быть перепланировано для выполнения с заданными интервалами (до тех пор, пока задание не будет выполнено успешно). Линейная задержка лучше всего подходит для работы, которая должна быть выполнена как можно скорее, или при наличии проблем, которые быстро исчезают сами по себе.

Дополнительные сведения о создании объекта JobInfo см. в документации Google по классу JobInfo.Builder.

Передача параметров в задание через JobInfo

Параметры передаются в задание путем создания PersistableBundle, который передается вместе с методом Job.Builder.SetExtras.

var jobParameters = new PersistableBundle();
jobParameters.PutInt("LoopCount", 11);

var jobBuilder = this.CreateJobBuilderUsingJobId<DownloadJob>(1)
                     .SetExtras(jobParameters)
                     .Build();

Доступ к PersistableBundle осуществляется из свойства Android.App.Job.JobParameters.Extras метода OnStartJob класса JobService.

public override bool OnStartJob(JobParameters jobParameters)
{
    var loopCount = jobParams.Extras.GetInt("LoopCount", 10);
    
    // rest of code omitted
} 

Планирование задания

Чтобы запланировать задание, приложение Xamarin.Android получает ссылку на системную службу JobScheduler и вызывает метод JobScheduler.Schedule для объекта JobInfo, который был создан на предыдущем шаге. JobScheduler.Schedule немедленно возвращает одно из двух целочисленных значений:

  • JobScheduler.ResultSuccess — задание успешно запланировано.
  • JobScheduler.ResultFailure — задание не удалось запланировать. Обычно это связано с конфликтом параметров JobInfo.

Следующий код содержит пример планирования задания и уведомления пользователя о результатах попытки планирования:

var jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
var scheduleResult = jobScheduler.Schedule(jobInfo);

if (JobScheduler.ResultSuccess == scheduleResult)
{
    var snackBar = Snackbar.Make(FindViewById(Android.Resource.Id.Content), Resource.String.jobscheduled_success, Snackbar.LengthShort);
    snackBar.Show();
}
else
{
    var snackBar = Snackbar.Make(FindViewById(Android.Resource.Id.Content), Resource.String.jobscheduled_failure, Snackbar.LengthShort);
    snackBar.Show();
}

Отмена задания

Можно отменить все запланированные задания или только одно задание, используя метод JobsScheduler.CancelAll() или метод JobScheduler.Cancel(jobId).

// Cancel all jobs
jobScheduler.CancelAll(); 

// to cancel a job with jobID = 1
jobScheduler.Cancel(1)

Итоги

В этом руководстве описано, как использовать планировщик заданий Android для рационального выполнения работы в фоновом режиме. Мы изучили, как инкапсулировать в JobService работу, которую необходимо выполнить, и как с помощью JobScheduler запланировать эту работу, указывая критерии в JobTrigger и способ обработки ошибок в RetryStrategy.