Как стать автором
Обновить
657.78
OTUS
Цифровые навыки от ведущих экспертов

Cервисы в Android

Время на прочтение11 мин
Количество просмотров1.8K

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

Сервис или служба — это компонент приложения, который может выполнять длительные операции в фоновом режиме, но при этом он не предоставляет пользовательского интерфейса. После запуска служба может продолжать работать в течение некоторого времени, даже после того, как пользователь переключится на другое приложение. Кроме того, компоненты других приложений могут связываться со службой, чтобы взаимодействовать с ней и даже выполнять межпроцессное взаимодействие (IPC). Например, служба может обрабатывать сетевые транзакции, воспроизводить музыку, выполнять ввод‑вывод файлов или взаимодействовать с поставщиком контента — и все это в фоновом режиме.

Виды сервисов

Наиболее заметными для пользователей являются службы переднего плана (Foreground), которые выполняют какие‑либо действия, видимые для пользователя. Например, аудиоприложение использует службу переднего плана для воспроизведения музыки. Эти службы продолжают работать, даже если пользователь не взаимодействует с приложением.

При использовании службы переднего плана необходимо отображать уведомление, чтобы пользователи были в курсе того, что служба запущена. Это уведомление не может быть отменено, пока служба не будет остановлена или удалена с переднего плана.

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

Привязка службы (Bound) используется, когда компонент приложения привязывается к ней, вызывая bindService(). Связанный сервис предлагает клиент‑серверный интерфейс, который позволяет компонентам взаимодействовать с сервисом, отправлять запросы, получать результаты и взаимодействовать между процессами с помощью межпроцессного взаимодействия (IPC). Связанный сервис работает только до тех пор, пока к нему привязан другой компонент приложения. Несколько компонентов могут привязываться к сервису одновременно, но когда все они отвязываются, сервис уничтожается.

Сервис или поток

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

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

Например, если вы хотите проигрывать музыку, но только во время выполнения вашей активности, вы можете создать поток в onCreate(), запустить его в onStart() и остановить в onStop(). Также можно рассмотреть возможность использования пулов потоков и исполнителей из пакета java.util.concurrent вместо традиционного класса Thread.

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

Разобравшись с типами сервисов, перейдем непосредственно к их созданию.

Создание сервиса

Чтобы создать сервис, вы должны создать подкласс Service или использовать один из его существующих подклассов. В своей реализации вы должны переопределить некоторые методы обратного вызова, которые обрабатывают ключевые аспекты жизненного цикла сервиса и обеспечивают механизм, позволяющий компонентам связываться с сервисом, если это необходимо.

Рассмотрим наиболее важные методы обратного вызова, которые вы должны переопределить.

Система вызывает метод onStartCommand(), при запуске startService(), когда другой компонент (например, одна из активностей) запрашивает запуск сервиса. Когда этот метод выполняется, служба запускается и может работать в фоновом режиме неограниченное время. Если вы реализуете эту функцию, вы обязаны остановить службу по завершении ее работы, вызвав stopSelf() или stopService(). Если вы хотите обеспечить только привязку, вам не нужно реализовывать этот метод.

Метод onBind() вызывается при обращении к bindService(), когда другой компонент хочет установить связь с сервисом (например, для выполнения RPC). В своей реализации этого метода вы должны предоставить интерфейс, который клиенты используют для связи со службой, возвращая IBinder. Вы всегда должны реализовывать этот метод; однако, если вы не хотите разрешать связывание, вы должны возвращать null.

Система вызывает метод onCreate() для выполнения одноразовых процедур настройки при первоначальном создании службы (до вызова onStartCommand() или onBind()). Если служба уже запущена, этот метод не вызывается.

Метод onDestroy() вызывается, когда служба больше не используется и уничтожается. Ваш сервис должен реализовать его, чтобы очистить все ресурсы, такие как потоки, зарегистрированные слушатели или приемники. Это последний вызов, который получает служба.

Если компонент запускает службу вызовом startService() (что приводит к вызову onStartCommand()), служба продолжает работать, пока не остановит себя вызовом stopSelf() или другой компонент не остановит ее вызовом stopService().

Если компонент вызывает bindService() для создания сервиса и onStartCommand() не вызывается, сервис работает только до тех пор, пока компонент связан с ним. После того как служба отвязана от всех своих клиентов, система уничтожает ее.

Сервисы и память

Напомним о принципах вытесняющей многозадачности, используемой в Android. По идее служба может функционировать весь сеанс работы операционной системы, начиная с запуска и до следующей перезагрузки. Но на практике такое бывает редко. В случае нехватки памяти система Android останавливает службу, когда она должна восстановить системные ресурсы для активности, находящейся в фокусе пользователя. Если служба привязана к активности, находящейся в фокусе пользователя, вероятность ее остановки ниже, так как она по сути сейчас используется. Поэтому если служба объявлена для выполнения на переднем плане, ее редко останавливают.

Но если служба запущена и работает долго, система со временем понижает ее позицию в списке фоновых задач, и вероятность ее уничтожения увеличивается. Так что, если ваша служба запущена, вы должны спроектировать ее так, чтобы она изящно обрабатывала перезапуски системы. Если система убивает вашу службу, она перезапускает ее, как только ресурсы становятся доступными.

Объявление службы в манифесте

Для начала нам необходимо объявить все сервисы в файле манифеста вашего приложения, так же как это делается для других компонентов.

Чтобы объявить службу, добавьте элемент <service> в качестве дочернего элемента <application>. Вот пример:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

Есть и другие атрибуты, которые можно включить в элемент <service> для определения таких свойств, как разрешения, необходимые для запуска сервиса, и процесс, в котором сервис должен быть запущен. Атрибут android:name является единственным обязательным атрибутом — он задает имя класса службы. После публикации приложения оставьте это имя неизменным, чтобы избежать риска нарушения кода из‑за зависимости от явных намерений запустить или привязать службу.

Для того, чтобы гарантировать, что ваш сервис будет доступен только вашему приложению, включите атрибут android:exported и установите для него значение false. Таким образом мы запрещаем другим приложениям запускать нашу службу, даже явным образом.

Реализация сервиса

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

Компонент приложения, например активность, может запустить сервис, вызвав startService() и передав ему Intent, которое определяет сервис и включает любые данные для использования сервисом. Служба получает этот Intent в методе onStartCommand().

Например, предположим, что активность должна сохранить некоторые данные в онлайн‑базе данных. Активность может запустить службу‑компаньона и передать ей данные для сохранения, передав намерение в startService(). Служба получает Intent в onStartCommand(), подключается к Интернету и выполняет транзакцию с базой данных. Когда транзакция завершена, служба останавливается и уничтожается.

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

Посмотрим, как может выглядеть базовая реализация класса HelloService

public class HelloService extends Service {
  private Looper serviceLooper;
  private ServiceHandler serviceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }

      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              // Restore interrupt status.
              Thread.currentThread().interrupt();
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }

  }

  @Override
  public void onCreate() {
    // Start up the thread running the service. Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block. We also make it
    // background priority so CPU-intensive work doesn't disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    serviceLooper = thread.getLooper();
    serviceHandler = new ServiceHandler(serviceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {

      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the

      // start ID so we know which request we're stopping when we finish the job
      Message msg = serviceHandler.obtainMessage();
      msg.arg1 = startId;
      serviceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override

  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

Код примера обрабатывает все входящие вызовы в onStartCommand() и передает работу обработчику, запущенному в фоновом потоке. Он работает так же, как IntentService, и обрабатывает все запросы последовательно, один за другим. Вы можете изменить код, чтобы запустить работу в пуле потоков, например, если хотите выполнять несколько запросов одновременно.

Обратите внимание, что метод onStartCommand() должен возвращать целое число. Целое число — это значение, которое описывает, как система должна продолжить работу сервиса в случае его уничтожения. Возвращаемое значение метода onStartCommand() должно быть одной из следующих констант:

START_NOT_STICKY

Если система убивает службу после возврата onStartCommand(), не создавайте ее заново, если только не осталось невыполненных интентов. Это самый безопасный вариант, позволяющий избежать запуска службы, когда в ней нет необходимости, и когда ваше приложение может просто перезапустить все незавершенные задания.

START_STICKY

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

START_REDELIVER_INTENT

Если система убивает службу после возврата onStartCommand(), создайте службу заново и вызовите onStartCommand() с последним интентом, которое было доставлено службе. Все ожидающие интенты будут доставлены по очереди. Это подходит для служб, которые активно выполняют задание, требующее немедленного возобновления, например загрузка файла.

Запуск и остановка сервиса

Запустить сервис можно различными способами. Так можно запустить службу из активности или другого компонента приложения, передав Intent в startService() или startForegroundService().

Запустим пример сервиса из предыдущего раздела (HelloService), с помощью startService(), как показано здесь:

startService(new Intent(this, HelloService.class));

Метод startService() немедленно вернет ответ, и система Android использует метод onStartCommand(). Если сервис еще не запущен, система сначала вызывает onCreate(), а затем onStartCommand().

Несколько запросов на запуск службы приводят к нескольким соответствующим вызовам onStartCommand() службы. Однако для остановки службы требуется только один запрос (с помощью stopSelf() или stopService()).

Запущенная служба должна управлять своим жизненным циклом. То есть система не останавливает и не уничтожает службу, если только ей не нужно восстановить системную память, и служба продолжает работать после возврата onStartCommand(). Служба должна остановиться сама, вызвав stopSelf(), или другой компонент может остановить ее, вызвав stopService().

После запроса на остановку с помощью функции stopSelf() или stopService() система как можно скорее уничтожает службу.

Заключение

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


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

Теги:
Хабы:
0
Комментарии4

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS