Урок 94. Service. Подробно про onStartCommand
Метод onStartCommand должен возвращать int. В прошлых примерах мы использовали ответ метода супер-класса. В этом уроке разберемся, что мы можем возвращать, и чем нам это грозит. Также посмотрим, что за флаги (второй параметр) идут на вход этому методу.
Система может убить сервис, если ей будет не хватать памяти. Но в наших силах сделать так, чтобы наш сервис ожил, когда проблема с памятью будет устранена. И более того, не просто ожил, а еще и снова начал выполнять незавершенные вызовы startService.
Т.е. мы вызываем startService, срабатывает onStartCommand и возвращает одну из следующих констант:
START_NOT_STICKY – сервис не будет перезапущен после того, как был убит системой
START_STICKY – сервис будет перезапущен после того, как был убит системой
START_REDELIVER_INTENT – сервис будет перезапущен после того, как был убит системой. Кроме этого, сервис снова получит все вызовы startService, которые не были завершены методом stopSelf(startId).
А второй параметр flags метода onStartCommand дает нам понять, что это повторная попытка вызова onStartCommand.
В хелпе написано, что flags может принимать значения 0, START_FLAG_RETRY или START_FLAG_REDELIVERY.
На практике я ни разу не встречал значения 0. Постоянно приходит START_FLAG_RETRY. По этому поводу я видел в инете мнение, что это реальный баг системы.
А вот на флаг START_FLAG_REDELIVERY можно положиться. Если он пришел вам в методе onStartCommand, значит, прошлый вызов этого метода вернул START_REDELIVER_INTENT, но не был завершен успешно методом stopSelf(startId).
В общем, на словах трудно все это объяснять, сейчас нарисуем приложение, и все станет понятнее.
Будем создавать два проекта - соответственно, два Application-а у нас получится. В первом будет только одно Activity, которое будет вызывать сервис. А во втором собственно и будет этот сервис. Т.е. в прошлых уроках у нас приложение и сервис были в одном Application, а в этом уроке мы их раскидаем по двум разным Application. И сейчас даже объясню, зачем мы так сделаем.
В начале урока я написал «Система может убить сервис». Этим мы и будем сейчас заниматься, чтобы на практике проверить поведение сервиса в таких случаях. Мы будем убивать процесс, который отвечает за сервис. Если бы у нас сервис и приложение были бы в одном Application, то они выполнялись бы в одном процессе. И убив процесс, мы грохнули бы и сервис и приложение. А нам надо убить только сервис, поэтому выносим его в отдельное Application, и он в своем процессе будет работать.
Я раскидал сервис и приложение по разным проектам для большей наглядности. А вообще, организовать работу сервиса и приложения в разных процессах можно было и в одном проекте (в одном Application). Для этого в манифесте для сервиса надо прописать атрибут: android:process=":newproc". Вместо newproc можно использовать свое слово.
Итак, создадим первый проект:
Project name: P0941_ServiceKillClient Build Target: Android 2.3.3 Application name: ServiceKillClient Package name: ru.startandroid.develop.p0941servicekillclient Create Activity: MainActivity
Это у нас будет приложение-клиент, оно будет вызывать сервис.
Добавим в strings.xml строки:
Экран main.xml:
MainActivity.java:
Просто вызов сервиса. Вспоминаем способы вызова Activity, которые мы знаем, и понимаем, что здесь идет аналогичный вызов сервиса через Action. А значит, когда будем создавать сервис, надо будет настроить Intent Filter с этим Action. На прошлых уроках мы сервис вызывали через класс, т.к. там приложение и сервис в одном Application у нас были.
В Intent помещаем параметр с именем name и значением value. Это нам понадобится, чтобы убедиться, что система при восстановлении сервиса и повторном вызове не потеряет наши данные.
Создадим второй проект, без Activity:
Project name: P0942_ServiceKillServer Build Target: Android 2.3.3 Application name: ServiceKillServer Package name: ru.startandroid.develop.p0942servicekillserver
Уберите эту галку
Нам в этом Application не понадобится Activity.
В втором проекте создаем сервис, класс MyService.java:
Теперь будем возвращать START_STICKY. Это значит, что сервис будет восстановлен после убийства.
Все сохраняем, запускаем (CTRL+F11). Наш сервис инсталлится на эмулятор. А приложение так и работает до сих пор.
Жмем Start и сразу же в процессах убиваем появившийся процесс сервиса. Но! Фокус еще не закончен. Проходит несколько секунд (у меня около 5, у вас может быть по-другому), и процесс снова появляется в списке! Система восстановила убитый сервис и сколько вы его теперь не грохайте, он постоянно будет восстанавливаться. Это нам обеспечила константа START_STICKY, которую мы возвращаем в onStartCommand.
MyService onCreateMyService onStartCommand, name = valueSTART_FLAG_RETRYMyRun#1 createMyRun#1 start
Почти все так же, как и в прошлый раз. Сервис запустился, получил вызов startService, создал и запустил MyRun. Потом мы его убили - в логах это никак не отражено.
MyService onCreate
И далее сервис снова создан (MyService onCreate). Но вызов startService, который он получил в прошлый раз, потерян.
Для того, чтобы этот вызов не терялся, нужна константа START_REDELIVER_INTENT. Будем возвращать ее в методе onStartCommand:
Сохраняем, инсталлим сервис. Жмем Start и убиваем процесс. Процесс снова появляется спустя какое-то время. Смотрим логи:
MyService onCreate MyService onStartCommand, name = value START_FLAG_RETRY MyRun#1 create MyRun#1 start
Сервис начал работу и был убит, как в прошлые разы.
MyService onCreate MyService onStartCommand, name = value START_FLAG_REDELIVERY START_FLAG_RETRY MyRun#1 create MyRun#1 start MyRun#1 end, stopSelfResult(1) = true MyService onDestroy
А далее он был снова запущен, снова получил вызов startService и успешно его отработал вплоть до stopSelfResult. После чего сервис сам остановился. Обратите внимание, что Intent мы получили тот же, что и в первую попытку, с параметром name = value. Т.е. система сохранила все, а когда процесс был убит, восстановила его и вернула нам данные.
В этот раз кроме START_FLAG_RETRY мы получили еще флаг START_FLAG_REDELIVERY, который говорит нам о том, что мы уже получали такой вызов, но в прошлый раз он по каким-то причинам не завершился методом stopSelf или stopSelfResult.
Если во время убийства сервиса будет висеть несколько таких работающих START_FLAG_REDELIVERY вызовов, для которых еще не был выполнен stopSelf, то все они будут снова отправлены в сервис при его восстановлении.
Вот такие три замечательные константы, которые позволяют нам управлять поведением сервиса после его насильственной смерти.
Наверняка возникает вопрос типа «Как поведет себя сервис, если отправить ему несколько вызовов и в каждом onStartCommand возвращать разные константы». Я в одном уроке не смогу рассмотреть все эти комбинации и варианты. Предлагаю потестить вам это самостоятельно.
И еще имеет смысл сказать, что метод onStartCommand появился только начиная с Android 2.0. До этого использовался более простой onStart, без механизма восстановления.
На следующем уроке:
- получаем из сервиса результат с помощью PendingIntent
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
- новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме