В продолжении темы прошлого номера мы узнаем, каким образом в Android можно генерировать дополнительные потоки и зачем это может понадобиться. Сегодня поговорим о таком компоненте приложения, как сервисы, и опишем их роли в мобильном мире Android. Сервисы и потоки тесно переплетены между собой, поэтому, если ты еще не читал прошлый номер «Хакера», начни с моей статьи, будет полезно!

 

Сервисы

Компания Google в своей статье Application Fundamentals, обращенной к будущим Android-разработчикам, выделяет четыре базовых компонента Android-приложения: Activity, Service, поставщики содержимого (Content Providers) и приемники широковещательных сообщений (BroadCast Recievers). С Activity начинается знакомство с разработкой, о последних двух компонентах «Хакер» писал раньше (и еще обязательно к ним вернется), поэтому сейчас поговорим о сервисах.

Большинство мобильных устройств обладают достаточно скромными ресурсами, а значит, ОС приходится постоянно перераспределять их между работающими приложениями. Если в системе нет свободной памяти для нового запущенного приложения, то ОС принудительно завершает Activity, которые находятся в состояниях onStop и onPause, а вместе с ними и их дополнительные потоки.

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

И тут на помощь приходят сервисы! По сути дела, это те же самые Activity, но без графических элементов. Ввиду отсутствия UI они предназначены для длительных операций, которые могут долгое время выполняться без участия пользователя. Проигрывание музыки, запись данных для фитнес-трекера, продолжительный сетевой обмен — все это задачи для сервисов.

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

Рис. 1. Жизненный цикл сервисов (c) Google
Рис. 1. Жизненный цикл сервисов (c) Google

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

 

IntentService

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

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

public class SimpleIntentService extends IntentService {

Полезная нагрузка размещается в методе onHandleIntent(), который будет выполнен системой сразу после старта сервиса. После завершения работы этого метода сервис будет остановлен системой, а ресурсы освобождены.

protected void onHandleIntent(Intent intent) {
  try {
    String url=intent.getStringExtra("link");
    Thread.sleep(5000);
    Log.e("IntentService", "data loaded to" + url);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
}

Передача данных, как и запуск самого сервиса, происходит через уже известный механизм намерений (Intent).

Intent intent = new Intent(getApplicationContext(), SimpleIntentService.class);
intent.putExtra("link", "url_to_load");
startService(intent);

Любой сервис, как и Activity, необходимо зарегистрировать в манифест-файле, иначе ОС о нем не узнает и он просто не будет запущен.

<service android:name=".SimpleIntentService" />

Все готово! После выполнения метода startService в работающем приложении появится новый поток, который выполнит необходимые действия, не загружая UI. Но простота реализации приносит и свои минусы:

  • Отсутствует явная связь с главным потоком. После старта сервис начинает жить своей жизнью, и вернуть результаты вычислений обратно в Activity можно только с помощью широковещательных сообщений.
  • IntentService подходит для выполнения редких, разовых операций, поскольку его конструкция не позволяет выполнять несколько задач одновременно. Если IntentService уже чем-то занят, то при повторном запуске сервиса будет организована очередь и вычисления будут выполнены последовательно.
  • Операции, выполняемые в IntentService, не могут быть прерваны, а значит, сервис будет висеть в памяти до тех пор, пока не завершатся все действия, задуманные в методе onHandleIntent.
 

Service

А теперь настало время познакомиться с родителями :). Я сознательно начал разговор с наследника — IntentService, поскольку он предельно прост в эксплуатации: просит мало, работает долго и уходит незаметно. С «оригинальным» же Service все сложнее, чем кажется поначалу.

В статье Services, где разработчики Google рассказывают о своем детище, он назван компонентом, который позволяет выполнять длительные операции в фоне. Прочитав вступление, испытываешь огромный соблазн сразу же накидать сервисов в приложение и выкатить релиз. Но не все так просто — к примеру, портал Stack Overflow завален вопросами вроде «почему мои сервисы постоянно выдают ANR-сообщения?».

Рис. 2. Более 60 тысяч вопросов по сервисам в Android
Рис. 2. Более 60 тысяч вопросов по сервисам в Android

Оказывается, изначально объект класса Service не создает для себя новый поток, а выполняется там, где его инициализировали! Поэтому, создав в MainActivity новый сервис, разработчик довольно быстро подвесит UI. Чтобы этого избежать, необходимо внутри сервиса самостоятельно генерировать потоки (AsyncTask, Loader и так далее — выбирай любой) и помещать ресурсозатратные вычисления в них.

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

Недавно «Хакер» рассказывал, что в Android достаточно легко написать зловред, который бы шифровал пользовательские данные стойким ключом. Тогда мы не акцентировали свое внимание на производительности — файлы шифровались последовательно, один за другим. Сейчас посмотрим, как с помощью сервиса можно сократить время выполнения операций шифрования.

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

Продолжение статьи доступно только подписчикам

Вариант 1. Оформи подписку на «Хакер», чтобы читать все статьи на сайте

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

Вариант 2. Купи одну статью

Заинтересовала статья, но нет возможности оплатить подписку? Тогда этот вариант для тебя! Обрати внимание: этот способ покупки доступен только для статей, опубликованных более двух месяцев назад.


Комментарии

Подпишитесь на ][, чтобы участвовать в обсуждении

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

Check Also

Используй, свободно! Как работает уязвимость use-after-free в почтовике Exim

В самом популярном на сегодняшний день почтовом сервере Exim был обнаружен опасный баг: ес…