Как остановить работающий started service
Перейти к содержимому

Как остановить работающий started service

  • автор:

Урок 92. Service. Простой пример

Для начала надо определиться, как по-русски называть Service. Общепринятый перевод – служба. Наиболее популярный пример – службы в Windows. Но для системы Android мне привычнее использовать слово сервис. Его я и буду использовать в своих уроках для обозначения Service.

И еще один момент надо сразу прояснить. Service прописывается в манифесте рядом с Activity, и получается, что приложение (Application) содержит в себе и Activity и сервис. Предлагаю для упрощения изложения материала под словом приложение понимать все таки только Activity, которые можно запустить и увидеть на экране. Т.е. то, что мы раньше и называли приложением. А сервис считать отдельной от приложения вещью. А если надо будет обозначить приложение, как контейнер для Activity и сервиса, буду использовать слово Application.

Т.е. приложение – это набор Activity, сервис – Service. Приложение + сервис = Application. Как то так.

Итак, сервис – это некая задача, которая работает в фоне и не использует UI. Запускать и останавливать сервис можно из приложений и других сервисов. Также можно подключиться к уже работающему сервису и взаимодействовать с ним.

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

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

Project name: P0921_ServiceSimple
Build Target: Android 2.3.3
Application name: ServiceSimple
Package name: ru.startandroid.develop.p0921servicesimple
Create Activity: MainActivity

Добавим в strings.xml строки:

Start service Stop service 

Экран main.xml:

Две кнопки – для запуска и остановки сервиса. И ProgressBar.

Рядом с MainActivity создайте класс MyService, наследующий android.app.Service

Содержимое MyService.java:

package ru.startandroid.develop.p0921servicesimple; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; public class MyService extends Service < final String LOG_TAG = "myLogs"; public void onCreate() < super.onCreate(); Log.d(LOG_TAG, "onCreate"); >public int onStartCommand(Intent intent, int flags, int startId) < Log.d(LOG_TAG, "onStartCommand"); someTask(); return super.onStartCommand(intent, flags, startId); >public void onDestroy() < super.onDestroy(); Log.d(LOG_TAG, "onDestroy"); >public IBinder onBind(Intent intent) < Log.d(LOG_TAG, "onBind"); return null; >void someTask() < >>

У сервиса так же, как и у Activity есть методы onCreate и onDestroy – которые срабатывают при создании и уничтожении сервиса.

Метод onStartCommand – срабатывает, когда сервис запущен методом startService. В нем мы запускаем наш метод someTask, который пока пуст. У onStartCommand на вход и на выход идут параметры, мы их пока не используем.

Метод onBind нам пока не интересен. Но реализовать его мы обязаны, возвращаем null.

someTask – здесь будем кодить работу для сервиса

Сервис, как и новые, создаваемые Activity необходимо прописать в манифесте. Делается это полностью аналогично. Жмете Add, выбираете Service. И в поле Name выбираете MyService.

MainActivity.java:

package ru.startandroid.develop.p0921servicesimple; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; public class MainActivity extends Activity < final String LOG_TAG = "myLogs"; public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.main); >public void onClickStart(View v) < startService(new Intent(this, MyService.class)); >public void onClickStop(View v) < stopService(new Intent(this, MyService.class)); >>

Здесь у нас два метода, которые срабатывают при нажатии на кнопки Start service и Stop service. В них мы соответственно запускаем или останавливаем сервис методами startService и stopService. На вход передаем Intent, указывающий на сервис. Это очень похоже на то, как мы вызываем Activity методом startActivity.

Давайте все сохраним и запустим приложение.

Нажмем Start service и смотрим лог:

onCreate
onStartCommand

Выполнился метод onCreate – сервис создался, и onStartCommand – сервис выполняет содержимое метода onStartCommand.

Если мы теперь еще раз нажмем Start service:

onStartCommand

Сервис уже создан, onCreate не вызывается, выполняется только метод onStartCommand.

Жмем Stop service

Убедимся, что сервис не зависит от приложения. Жмем Start service.

onCreate
onStartCommand

Сервис запущен. Закрываем приложение кнопкой Назад. В логах тишина, onDestroy в сервисе не выполнился, сервис продолжает жить. Ему все равно, работает приложение, его запустившее или не работает.

Долгим удержанием клавиши Домой выведем список последних приложений

снова откроем наше приложение и нажмем Stop service. В логах:

Теперь попробуем выполнять что-нибудь осмысленное в onStartCommand. Перепишем метод someTask в MyService.java:

Перепишем метод someTask:

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

Служба (Service)

Службы (Сервисы) в Android работают как фоновые процессы и представлены классом android.app.Service. Они не имеют пользовательского интерфейса и нужны в тех случаях, когда не требуется вмешательства пользователя. Сервисы работают в фоновом режиме, выполняя сетевые запросы к веб-серверу, обрабатывая информацию, запуская уведомления и т.д. Служба может быть запущена и будет продолжать работать до тех пор, пока кто-нибудь не остановит её или пока она не остановит себя сама. Сервисы предназначены для длительного существования, в отличие от активностей. Они могут работать, постоянно перезапускаясь, выполняя постоянные задачи или выполняя задачи, требующие много времени.

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

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

Используя сервис, можете быть уверены, что ваши приложения продолжат работать и реагировать на события, даже если они в неактивном состоянии. Для работы службам не нужен отдельный графический интерфейс, как в случае с активностями, но они по-прежнему выполняются в главном потоке хода приложения. Чтобы повысить отзывчивость вашего приложения, нужно уметь переносить трудоёмкие процессы (например, сетевые запросы) в фоновые потоки, используя классы Thread и AsyncTask.

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

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

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

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

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

Создание службы

Чтобы определить службу, необходимо создать новый класс, расширяющий базовый класс Service. Можно воспользоваться готовым мастером создания класса для сервиса в Android Studio. Щёлкаем правой кнопкой мыши на папке java (или на имени пакета) и выбираем New | Service | Service:

Service

В следующем окне выбираем имя сервиса (флажки оставляем) и нажимаем кнопку Finish.

Create new Service

 // Kotlin package ru.alexanderklimov.service import android.app.Service import android.content.Intent import android.os.IBinder class MyService : Service() < override fun onBind(intent: Intent): IBinder < TODO("Return the communication channel to the service.") >> 
// Java package ru.alexanderklimov.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; public class MyService extends Service < public MyService() < >@Override public IBinder onBind(Intent intent) < // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); >>

При этом сервис автоматически зарегистрируется в манифесте в секции .

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

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

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

Жизненный цикл служб

Подобно активностям служба имеет свои методы жизненного цикла:

Жизненный цикл Service

Для быстрого создания заготовок нужных методов используйте команду меню Code | Override Methods. или набирайте сразу имя метода, используя автодополнение.

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

  • полная целая жизнь службы — промежуток между временем вызова метода onCreate() и временем возвращения onDestroy(). Подобно активности, для служб производят начальную инициализацию в onCreate() и освобождают все остающиеся ресурсы в onDestroy()
  • активная целая жизнь службы — начинается с вызова метода onStartCommand(). Этому методу передаётся объект Intent, который передавали в метод startService().

Из своего приложения службу можно запустить вызовом метода Context.startService(), остановить через Context.stopService(). Служба может остановить сама себя, вызывая методы Service.stopSelf() или Service.stopSelfResult().

Можно установить подключение к работающей службе и использовать это подключение для взаимодействия со службой. Подключение устанавливают вызовом метода Context.bindService() и закрывают вызовом Context.unbindService(). Если служба уже была остановлена, вызов метода bindService() может её запустить.

Методы onCreate() и onDestroy() вызываются для всех служб независимо от того, запускаются ли они через Context.startService() или Context.bindService().

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

  • IBinder onBind(Intent intent)
  • onUnbind(Intent intent)
  • onRebind(Intent intent)

В метод обратного вызова onBind() передают объект Intent, который был параметром в методе bindService(), а в метод обратного вызова onUnbind() — объект Intent, который передавали в метод unbindService(). Если служба разрешает связывание, метод onBind() возвращает канал связи, который используют клиенты, чтобы взаимодействовать со службой. Метод обратного вызова onRebind() может быть вызван после onUnbind(), если новый клиент соединяется со службой.

Запуск сервиса и управление его перезагрузкой

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

Метод onStartCommand() заменяет устаревший метод onStart(), который использовался в Android 2.0. В отличие от onStart() новый метод позволяет указать системе, каким образом обрабатывать перезапуски, если сервис остановлен системой без явного вызова методов stopService() или stopSelf().

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

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

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

  • START_STICKY — Описывает стандартное поведение. Похоже на то, как был реализован метод onStart() в Android 2.0. Если вы вернёте это значение, обработчик onStartCommand() будет вызываться при повторном запуске сервиса после преждевременного завершения работы. Обратите внимание, что аргумент Intent, передаваемый в onStartCommand(), получит значение null. Данный режим обычно используется для служб, которые сами обрабатывают свои состояния, явно стартуя и завершая свою работу при необходимости (с помощью методов startService() и stopService()). Это относится к службам, которые проигрывают музыку или выполняют другие задачи в фоновом режиме
  • START_NOT_STICKY — Этот режим используется в сервисах, которые запускаются для выполнения конкретных действий или команд. Как правило, такие службы используют stopSelf() для прекращения работы, как только команда выполнена. После преждевременного прекращения работы службы, работающие в данном режиме, повторно запускаются только в том случае, если получат вызовы. Если с момента завершения работы Сервиса не был запущен метод startService(), он остановится без вызова обработчика onStartCommand(). Данный режим идеально подходит для сервисов, которые обрабатывают конкретные запросы, особенно это касается регулярного выполнения заданных действий (например, обновления или сетевые запросы). Вместо того, чтобы перезапускать сервис при нехватке ресурсов, часто более целесообразно позволить ему остановиться и повторить попытку запуска по прошествии запланированного интервала
  • START_REDELIVER_INTENT — В некоторых случаях нужно убедиться, что команды, которые вы посылаете сервису, выполнены. Этот режим — комбинация предыдущих двух. Если система преждевременно завершила работу сервиса, он запустится повторно, но только когда будет сделан явный запрос на запуск или если процесс завершился до вызова метода stopSelf(). В последнем случае вызовется обработчик onStartCommand(), он получит первоначальное намерение, обработка которого не завершилась должным образом.

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

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

Изначально намерение выступает в качестве параметра, который передастся в метод startService() при запуске сервиса. После перезапуска системой он может иметь значение null (если установлен режим START_STICKY) или оригинальное (если установлен флаг START_REDELIVER_INTENT).

Параметр flag может помочь узнать, как именно был запущен сервис:

  • START_FLAG_REDELIVERY — указывает на то, что параметр Intent повторно передан при принудительном завершении работы сервиса перед явным вызовом метода stopSelf()
  • START_FLAG_RETRY — указывает на то, что сервис повторно запущен после непредвиденного завершения работы; передается в том случае, если ранее сервис работал в режиме START_STICKY
 @Override public int onStartCommand(Intent intent, int flags, int startId) < if ((flags & START_FLAG_RETRY) == 0) < // TODO Если это повторный запуск, выполнить какие-то действия. >else < // TODO Альтернативные действия в фоновом режиме. >return Service.START_STICKY; > 

Запуск и остановка служб

Начиная с Android 8.0 вы можете получить исключение IllegalStateException, если у приложения нет разрешения на запуск от системы.

Чтобы запустить службу, в клиентском приложении необходимо вызывать метод startService(). Существует два способа вызова службы:

  • явный вызов;
  • неявный вызов

Пример для явного вызова службы с именем MyService:

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

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

Пример неявного вызова службы:

 startService(new Intent(MyService.SERVICE_ACTION)); 

Чтобы использовать этот пример, необходимо включить константу SERVICE_ACTION, идентифицирующую службу, в класс MyService, например:

private static String SERVICE_ACTION = "ru.alexanderklimov.media.PLAYER";

Используйте фильтр намерений, чтобы зарегистрировать его как провайдера SERVICE_ACTION. Если служба потребует разрешений, которые не имеет ваше приложение, то запрос вызовет исключение SecurityException.

Как только сервис завершил выполнение тех действий, для которых он запускался, вы должны вызвать метод stopSelf() либо без передачи параметра, чтобы ускорить остановку работы, либо передав значение startId, чтобы убедиться, что задачи выполнены для всех экземпляров, запущенных с помощью вызова startService(), как показано в следующем фрагменте:

 stopSelf(startId); 

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

Для остановки работы используйте метод stopService(), передавая ему объект Intent, определяющий нужный сервис.

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

Давайте создадим практическое приложение для работы со службой. Наша служба будет запускать на воспроизведение музыкальный файл, который будет проигрываться в фоновом режиме. Управлять службой можно будет из активности. Создайте новый проект. Для службы создайте отдельный класс PlayService. Служба будет загружать музыкальный файл sample.mp3 из каталога res/raw/ (разместите там свой MP3-файл).

PlayService.java

 package ru.alexanderklimov.service; import android.app.Service; import android.content.Intent; import android.media.MediaPlayer; import android.os.IBinder; import android.support.annotation.Nullable; import android.widget.Toast; public class PlayService extends Service < private MediaPlayer mPlayer; @Nullable @Override public IBinder onBind(Intent intent) < return null; >@Override public void onCreate() < super.onCreate(); Toast.makeText(this, "Служба создана", Toast.LENGTH_SHORT).show(); mPlayer = MediaPlayer.create(this, R.raw.flower_romashka); mPlayer.setLooping(false); >// Устаревший метод // @Override // public void onStart(Intent intent, int startId) < // super.onStart(intent, startId); // Toast.makeText(this, "Служба запущена", // Toast.LENGTH_SHORT).show(); // mPlayer.start(); // >@Override public int onStartCommand(Intent intent, int flags, int startId) < Toast.makeText(this, "Служба запущена", Toast.LENGTH_SHORT).show(); mPlayer.start(); return super.onStartCommand(intent, flags, startId); >@Override public void onDestroy() < super.onDestroy(); Toast.makeText(this, "Служба остановлена", Toast.LENGTH_SHORT).show(); mPlayer.stop(); >> 

Зарегистрируем службу в файле манифеста.

В файле разметки для активности определим две кнопки: Старт и Стоп:

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

 package ru.alexanderklimov.service; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity < @Override protected void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final Button btnStart = findViewById(R.id.button_start); final Button btnStop = findViewById(R.id.button_stop); // запуск службы btnStart.setOnClickListener(new View.OnClickListener() < @Override public void onClick(View view) < // используем явный вызов службы startService( new Intent(MainActivity.this, PlayService.class)); >>); // остановка службы btnStop.setOnClickListener(new View.OnClickListener() < @Override public void onClick(View view) < stopService( new Intent(MainActivity.this, PlayService.class)); >>); > > 

Запущенная служба будет выполняться независимо от состояния активности, несмотря на то, что эти компоненты находятся в одном приложении: если её завершить, служба все равно останется работать.

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

 public class BootBroadcast extends BroadcastReceiver < @Override public void onReceive(Context context, Intent intent) < context.startService(new Intent(context, TestService.class)); >> 

Приёмник регистрируется в манифесте с именем действия BOOT_COMPLETED:

Пример для Kotlin

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

 package ru.alexanderklimov.service import android.app.Service import android.content.Intent import android.os.Handler import android.os.IBinder import java.util.* class RandomNumberService: Service() < private lateinit var mHandler: Handler private lateinit var mRunnable: Runnable override fun onBind(intent: Intent): IBinder? < throw UnsupportedOperationException("Not yet implemented") >override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int < // Send a notification that service is started toast("Service started.") // Do a periodic task mHandler = Handler() mRunnable = Runnable < showRandomNumber() >mHandler.postDelayed(mRunnable, 5000) return START_STICKY > override fun onDestroy() < super.onDestroy() toast("Service destroyed.") mHandler.removeCallbacks(mRunnable) >// Custom method to do a task private fun showRandomNumber() < val rand = Random() val number = rand.nextInt(100) toast("Random Number : $number") mHandler.postDelayed(mRunnable, 5000) >> 

Разместите на экране активности три кнопки: запуск, остановка и статус службы. Код для активности.

 package ru.alexanderklimov.service import android.app.ActivityManager import android.content.Context import android.content.Intent import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() < override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val serviceClass = RandomNumberService::class.java val intent = Intent(this, serviceClass) startButton.setOnClickListener < // If the service is not running then start it if (!isServiceRunning(serviceClass)) < startService(intent) >else < toast("Service already running.") >> stopButton.setOnClickListener < if (isServiceRunning(serviceClass)) < stopService(intent) >else < toast("Service already stopped.") >> statButton.setOnClickListener < if (isServiceRunning(serviceClass)) < toast("Service is running.") >else < toast("Service is stopped.") >> > // Custom method to determine whether a service is running private fun isServiceRunning(serviceClass: Class): Boolean < val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager // Loop through the running services for (service in activityManager.getRunningServices(Integer.MAX_VALUE)) < if (serviceClass.name == service.service.className) < // If the service is running then return true return true >> return false > > // Extension function to show toast message fun Context.toast(message: String)

Список всех запущенных сервисов

 ActivityManager am = (ActivityManager) this .getSystemService(ACTIVITY_SERVICE); List rs = am.getRunningServices(50); for (int i = 0; i

Ущемление прав службы

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

Для решения проблем следует изучить такие вещи как JobScheduler, Firebase Job Dispatcher, WorkManager. Также появилось понятие Foreground service.

Системные службы

Кроме создания собственных сервисов, вы можете использовать системные сервисы. Вот небольшой список:

  • Account Service — служба для управления пользовательскими учётными записями
  • Activity Service — служба для управления активностями
  • Alarm Service — служба для отправки разовых или периодических оповещений в заданное время
  • Bluetooth Service — служба для Bluetooth
  • Clipboard Service — служба для управления буфером обмена
  • Connectivity Service — служба для управления сетевыми соединениями
  • Download Service — служба для управления загрузками
  • Input Method Service — служба для управления текстовым вводом
  • JobScheduler — служба для планирования задач
  • Location Service — служба для отслеживания координат
  • Layout Inflater Service — служба для управления компоновкой экрана при динамическом создании из кода
  • NFC Service — служба для управления NFC
  • Notification Service — служба для управления уведомлениями
  • Power Service — служба для управления энергопотреблением
  • Search Service — служба для управления глобальным поиском
  • Sensor Service — служба для доступа к датчикам
  • Telephony Service — служба для управления телефонными функциями
  • Vibrator Service — служба для доступа к виброзвонку
  • Wallpaper Service — служба для управления обоями на домашнем экране
  • Wifi Service — служба для управления соединениями Wi-Fi

Delphi разработка под Android

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

понедельник, 12 июля 2021 г.

[Android Service] Пример Started сервиса

Пример очень простой и призван показать самые основы работы с сервисом.

Создание основы для дальнейшей разработки:

1. Создаём «Local сервис» с помощью «Мастера создания сервиса»

2. Переименовываем файл «Unit1.pas» в «uPlayService.pas», меняем имя проекта с «Project1» на «PlayService»

3. Сохраняем проект в отдельной папке «PlayService».

В этом примере структура каталогов будет такая:

4. Делаем сборку (Build) проекта для 32-bit и для 64-bit

5. Сохраняем и теперь уже закрываем проект.

6. Создаём новый проект (Multi-Device Application) приложения.

7. Переименовываем файл «Unit1.pas» в «MainForm.pas», меняем имя проекта с «Project1» на «AppForPlayService»

8. Сохраняем проект приложения в отдельную папку «App».

10. После добавления сохраняем проект приложения. Файл «ProjectGroup1.groupproj» сохраним в корневой папке «ProjectGroup».

Вот и всё, половину дела сделали. Теперь остаётся написать код, однако перед этим вернёмся к теории. Это необходимо сделать, чтобы понимать, какие/как/почему методы будем использовать.

Сервис запускается с помощью Android приложения и работает в одном потоке с приложением. Может работать в фоновом режиме неограниченное время, даже если приложение закрыто. Может быть «убит» и перезапущен (если мы явно указали это) системой (например, при нехватке оперативной памяти).

Жизненный цикл Started сервиса:

  1. onCreate()
  2. onStartCommand()
  3. onDestroy()

Запуск сервиса из приложения:

Остановка сервиса из приложения:

Остановка сервиса из самого сервиса:

Примечание 1. Если вы запустили сервис, то обязаны остановить его по завершении выполнения задачи или завершении приложения. Исключение, сервисы, которые должны постоянно работать (например, сервис, воспроизводящий музыку, сервис gps – трекинга и т.д.).

Примечание 2. Если сервис уже запущен, то система не будет запускать его второй раз, а просто передаст новые данные («Intent» и др. информацию) в метод «onStartCommand()».

Примечание 3. В прослойке «TLocalServiceConnection» я не обнаружил метода «stopService». Да и в документации тоже. Видимо, предполагается, что мы будем останавливать сервис только с помощью «stopSelf».

Теперь поговорим о методе onStartCommand().

1. Этот метод вызывается после метода OnCreate(), если сервис запустили впервые.

Примечание 5. Если сервис уже запущен и вы снова вызываете метод StartService(), то метод OnCreate() будет пропущен и сразу вызовется метод onStartCommand().

2. Этот метод вызывается всегда при запуске службы с помощью метода StartService().

3. В этом методе пишем код задачи, которую необходимо выполнить.

4. В этом методе мы можем получить доступ к параметрам:

  • Intent: JIntent; — интент с которым был вызван метод startService() и запущен сервис.
  • Flags: Integer – флаг, сообщающий о том, была ли повторная попытка запуска сервиса.(START_FLAG_REDELIVERY или START_FLAG_RETRY)
  • StartId: Integer – уникальное значение, относящееся к конкретному запуску. (используется совместно с методом stopSelfResult(int))

Метод должен вернуть одно из значений, указывающих системе как обрабатывать сервис:

  • START_NOT_STICKY – сервис не будет перезапущен системой, после того как будет убит системой.
  • START_REDELIVER_INTENT – сервис будет перезапущен в случае если его убила система. При этом будут переданы все незавершённые вызовы startService(). Отработают методы: OnCreate и onStartCommand().
  • START_STICKY – сервис будет пересоздан, если был убит системой. Все вызовы startService() будут потеряны. Метод onStartCommand() вернёт Intent равный nil. Отработают методы: OnCreate.

Теперь вернёмся к примеру.

Пример сервиса (в самом простейшем его виде):

  • Воспроизводит музыкальный трек при помощи Android API MediaPlayer
  • Принимает путь до трека переданный при помощи Intent’а и вызова startService()
  • Перезапускается (если был убит системой) с последним вызовом StartService().
  • Управление через приложение (кнопки Start, Stop, Next).
  • Запускать сервис можно передавая путь и не передавая путь (тогда будет воспроизведён трек по умолчанию).

Код файла MainForm.pas для приложения:

Код файла uPlayService.pas для сервиса:

Это простой пример (один из кучи возможных).

Для дополнительного изучения рекомендую:

  • Android API документацию по сервисам: Про сервис в манифесте и Про сервисы в целом
  • Документацию по сервисам в Delphi: Creating Android Services
  • Всемирную глобальную сеть Интернет, для поиска необходимой вам информации. Гуглите и всё найдётся 😉

p.s. К сожалению, все примеры и варианты использования Started сервиса не уместить в одной статье.

Сервисы Android: начало работы

Многие приложения Android имеют функции, выполнение которых требует много времени, например загрузка какого-либо контента или обновление удаленной базы данных. Если разработчики не организуют работу подобных функций должным образом, они постепенно загромоздят основной поток, создав утечки памяти и неточности в работе. Это может сильно раздражать ваших пользователей.

К счастью есть Android Services, которые позволяет сделать работу с подобным функционалом. Android Services — это особый тип компонента приложения, который предназначен для обработки повторяющихся и/или длительно выполняемых процессов.

  • В этом туториале вы узнаете:
  • Что такое Android Services и как их объявить в файле Manifest.
  • Как использовать foreground сервис для отображении уведомления пользователю.
  • Что такое фоновая обработка сложных процессов, не связанных с пользовательским интерфейсом.
  • Как использовать bound service.

Давайте начнем

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

В игре четыре уровня сложности: Beginner (Начальный), Intermediate (Средний), Advanced (Продвинутый ) и Expert (Эксперт). Чем выше уровень сложности, тем больше карт нужно сопоставить. После того, как пользователь выполняет задание на уровень эксперт, он проходит игру. Не хотите попробовать? :]

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

Откройте проект в Android Studio и ознакомьтесь с файлами. Когда вы закончите, запустите проект на устройстве / эмуляторе, чтобы посмотреть, как он выглядит.

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

Однако сначала давайте более подробно рассмотрим, что такое Android Services и как их использовать.

Представляем Android Services

Android Services — это компонент, который помогает выполнять длительные процессы, такие как обновление базы данных или сервера, запуск обратного отсчета или воспроизведение звука. По умолчанию Android Services запускаются в том же потоке, что и основные процессы приложения. Такой вид службы также называют local service.

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

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

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

Кроме того, другие компоненты приложения могут взаимодействовать с сервисами или даже осуществлять межпроцессное взаимодействие (InterProcess Communication (IPC)). Если вы будете дальше следовать этому туториалу, то узнаете больше о IPC.

В следующих разделах этого туториала мы применим каждый из перечисленных ниже типов Android Services и добавим в наше приложение Memo много новых функций:

  • Foreground service: Добавим сервис TimerService, который содержит логику для отслеживания времени.
  • Bound service: Используем MusicService, который позволит нам воспроизводить, ставить на паузу и перемешивать саундтреки.

Объявление сервиса в Manifest

Перед использованием сервиса его необходимо объявить в AndroidManifest.xml. Сервис — это компонент Android, точно так же, как и Activity. Для удобства наш проект уже содержит классы сервисов, но вам все еще необходимо правильно их конфигурировать.

Перейдите к AndroidManifest.xml и добавьте этот код внутрь application :

В этом кода вы можете увидеть следующее:

  • Android:name: Имя класса сервиса.
  • Android:enabled: Если установлено значение true, система может создавать экземпляры сервисов.
  • Android:exported: Если установлено значение False, то системе сообщается, что ни одно другое приложение не может запускать и использовать эти сервисы.

Значение android:name выделиться красным цветом, но пока просто не обращайте на это внимание. Мы починим это чуть позже.

А пока начнем работу по реализации нового функционала.

Реализация интерфейса сервиса

Каждый кастомный класс сервиса должен быть подклассом Service и должен реализовывать и переопределять несколько базовых методов колбэка, связанных с жизненным циклом сервиса.

Вот наиболее важные из них:

  • onStartCommand(): вызывается системой, когда другой компонент хочет запустить сервис, использую startService() . Как только система вызывает onStartCommand() , сервис может работать в фоновом режиме столько времени, сколько потребуется для завершения процесса. Вам нужно не забыть остановить службу вручную, когда работа будет завершена, вызвав stopSelf() или topService() . Эта не нужно, если вы хотите просто выполнить привязку к сервису, не запуская его.
  • onBind(): Вам всегда нужно применять этот метод колбэка. Система использует этот колбэк при вызове bindService() . Используйте его для привязки другого компонента к сервису. Он открывает возможность коммуникации, таким образом вы предоставляете интерфейс для взаимодействия клиентов возвращая IBinder . Если вы не хотите использовать привязку, просто верните null .
  • onCreate(): Используйте этот метод для настройки сервиса перед его запуском. Система вызывает его, только если сервис еще не запущен.
  • onDestroy(): Система вызывает его, чтобы уничтожить сервис, когда он больше не нужен. Это последний вызов, который получает сервис. После этого сервис перестает использовать какие-либо ресурсы.

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

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

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

Чтобы увидеть работу данного сервиса в действии давайте реализуем TimerService . Откройте TimerService.kt и добавьте следующее, чтобы сделать его подклассом Service :

class TimerService : Service(), CoroutineScope 

В этой строчке мы применяем интерфейс Service и CoroutineScope .

Применение методов сервиса.

Присмотритесь и обратите внимание, что Android Studio выдает ошибку из-за того, что не может найти onBind() .

Чтобы исправить это добавьте следующее в TimerService :

 // 1 override fun onBind(intent: Intent): IBinder? = null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int < super.onStartCommand(intent, flags, startId) // 2 intent?.extras?.run < when (getSerializable(SERVICE_COMMAND) as TimerState) < TimerState.START ->startTimer() TimerState.PAUSE -> pauseTimerService() TimerState.STOP -> endTimerService() else -> return START_NOT_STICKY > > // 3 return START_NOT_STICKY > override fun onDestroy() < super.onDestroy() // 4 handler.removeCallbacks(runnable) job.cancel() >

Давайте разберем этот код немного подробнее:

  1. Так как мы используем сервис переднего плана, нам не нужно выполнять привязку и мы возвращаем null вместо IBinder . Также заметьте, что мы не используем onCreate() , так как нам не нужна никакая специфическая конфигурация для этого сервиса.
  2. Здесь мы просматриваем дополнительные возможности Intent , чтобы получить значение, которое содержит ключ SERVICE_COMMAND . Это значение указывает на то, какое действие должен выполнить сервис.
  3. Если система завершает работу сервиса из-за нехватки памяти, START_NOT_STICKY говорит системе о том, что не стоит создавать службу с неопределенным Intent . Альтернативные константы: START_STICKY и START_REDELIVER_INTENT . Если вас интересует функционал этих констант, ознакомьтесь с официальной документацией по константам сервисов.
  4. Временно удалите все колбэки Handler , чтобы не засорять память. Кроме того, стоит очистить ресурсы, отменив все Job таймера, поскольку на следующем шаге сервис будет уже уничтожен.

Добавление разрешений

Все приложения, созданные для Android 9 (уровень API 28) или выше, должны запрашивать разрешение на использование службы переднего плана. Система автоматически разрешит доступ к сервису. В противном случае приложение генерирует исключение SecurityException.

В Project view перейдите к app ▸ manifest ▸ AndroidManifest.xml и добавьте следующий код над тегом application :

Это позволит нашему приложению Memo использовать сервис переднего плана.

Далее давайте создадим уведомление, которое будет говорить о работе этого сервиса.

Создание канала уведомлений

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

Откройте NotificationHelper.kt и добавьте этот код в начало класса:

private val notificationManager by lazy

Здесь вам не нужно инициализировать notificationManager , получив системную службу NOTIFICATION_SERVICE и преобразовав ее в NotificationManager .

В Android 8 и более поздних версиях уведомление должно быть частью канала уведомлений. Чтобы создать его, добавьте этот код в NotificationHelper.kt:

@RequiresApi(Build.VERSION_CODES.O) private fun createChannel() = // 1 NotificationChannel( CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT ).apply < // 2 description = CHANNEL_DESCRIPTION setSound(null, null) >

В этом кусочке кода мы:

  1. Создаем канал уведомлений с CHANNEL_ID в качестве идентификатора, CHANNEL_NAME в качестве имени и дефолтным приоритетом по умолчанию.
  2. Устанавливаем значение CHANNEL_DESCRIPTION для описания канала уведомлений, а для звука - значение null , что означает, что при срабатывании уведомления в канале звук не будет воспроизводиться.

Следующим шагом будет работа с отображением уведомлений.

Создание конструктора уведомлений

Чтобы пользователь знал о запущенном сервисе, создайте уведомление, которое пользователь сможет увидеть в строке состояния. Вверху NotificationHelper.kt добавьте:

 // 1 private val notificationBuilder: NotificationCompat.Builder by lazy < NotificationCompat.Builder(context, CHANNEL_ID) // 2 .setContentTitle(context.getString(R.string.app_name)) .setSound(null) .setContentIntent(contentIntent) .setSmallIcon(R.drawable.ic_launcher_foreground) .setPriority(NotificationCompat.PRIORITY_HIGH) // 3 .setAutoCancel(true) >

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

В этом коде мы реализовали следующее:

  1. Мы использовали NotificationCompat.Builder для того, чтобы создать конструктор уведомлений, отображаемых в строке состояния.
  2. Конструктор должен содержать информацию о некоторых параметрах уведомления. Однако вам не нужно определять эти параметры при объявлении конструктора. Если не будет хватать какой-либо информации, мы всегда сможем указать эту информацию в самом уведомлении.
  3. Этот тип уведомления должен быть настроен как автоматически отменяемый. Это означает, что когда пользователь нажмет на уведомление, оно автоматически закроется.
Заметка

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

Создание самого уведомления

Пришло время создать само уведомление. Добавьте в NotificationHelper.kt следующее:

 fun getNotification(): Notification < // 1 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) < notificationManager.createNotificationChannel(createChannel()) >// 2 return notificationBuilder.build() >

Вот что происходит в этом куске кода:

  1. Если версия Android равна 8 или выше, система создает канал уведомления и возвращает Notification . К сожалению, библиотека поддержки для версий до Android 8 не предоставляет API каналов уведомлений. Подробнее об этой проблеме читайте в официальной документации на тему каналов уведомлений.
  2. Возвращается Notification после вызова build() в конструкторе.

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

Ниже getNotification() добавьте следующее:

fun updateNotification(notificationText: String? = null) < // 1 notificationText?.let < notificationBuilder.setContentText(it) >// 2 notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()) >

Вот, что здесь происходит:

  1. Мы обновляем текст в уведомлении, отображаемом в строке состояния, через notificationBuilder .
  2. Затем говорим диспетчер уведомлений какое уведомление нужно обновить. Для этого мы используем уникальный NOTIFICATION_ID .

Наконец, давайте определим, что происходит, когда пользователь нажимает на уведомление. Добавьте это в начало NotificationHelper.kt:

 private val contentIntent by lazy

Здесь вы выполняете ленивую инициализацию PendingIntent , которая запускает MainActivity , когда пользователь нажимает на уведомление.

Прекрасная работа! Уже очень многое было реализовано. Теперь пора запустить Foreground Service.

Запуск и остановка службы

Чтобы запустить Foreground Service, используйте startForeground() . Для этого метода требуются два параметра: уникальный положительный целочисленный идентификатор уведомления и Notificaton .

Откройте TimerService.kt и добавьте следующее в метод startTimer() :

startForeground(NotificationHelper.NOTIFICATION_ID, helper.getNotification())

Этот метод заставляет службу работать на переднем плане и отправляет уведомление с идентификатором NOTIFICATION_ID в строку состояния. Вы заметите, что helper выделен красным, потому что вы еще не определили переменную helper .

Чтобы исправить это создайте экземпляр класса NotificationHelper в верхней части TimerService :

private val helper by lazy

Чтобы попытаться запустить сервис из другого компонента, откройте MainActivity.kt, найдите sendCommandToForegroundService() и добавьте:

ContextCompat.startForegroundService(this, getServiceIntent(timerState))

Здесь мы запускаем сервис из Activity. Вы передаете его Context и Intent для запуска сервиса.

Все, что начинается, должно заканчиваться, в том числе и сервисы. Откройте TimerService.kt и добавьте в stopService() следующее:

// 1 stopForeground(true) // 2 stopSelf()

В этом блоке кода:

  1. Этот вызов сообщает системе, что она должна убрать этот сервис из foreground. Переданный логический аргумент соответствует удалению уведомления, если для него установлено значение true .
  2. Поскольку сервис может запускаться сам, он также должен и завершаться сам. stopSelf() позволяет это реализовать.

Обновление уведомления при изменении таймера

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

Найдите BroadUpdate () внутри TimerService.kt и добавьте этот код в if :

// 1 sendBroadcast( Intent(TIMER_ACTION) .putExtra(NOTIFICATION_TEXT, elapsedTime) ) // 2 helper.updateNotification( getString(R.string.time_is_running, elapsedTime.secondsToTime()) )

Вот что делает этот блок кода:

  1. Здесь вы отправляете трансляцию с истекшим временем в MainActivity . С его помощью MainActivity может обновлять время в TextView .
  2. Этот вспомогательный метод обновляет уведомление в строке состояния, которое мы сделали ранее.

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

Чтобы это работало корректно, добавьте следующую строку в broadcastUpdate() в else if :

helper.updateNotification(getString(R.string.get_back))

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

Отличная работа! Запустите проект, чтобы проверить таймер. Запустите игру на каком-нибудь уровне сложности и обратите внимание, как внизу меняется таймер. Потяните вниз строку состояния, чтобы увидеть, как таймер изменяется в уведомлении. Попробуйте выйти из приложения, чтобы проверить обновление текста сообщения в уведомлении.

Использование фоновой обработки для сложных задач

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

Для этого вы можете использовать:

  1. Пользовательский Background Service - по умолчанию сервис запускается в потоке пользовательского интерфейса. Чтобы избежать блокировки, создайте Сервис с обработкой процесса в фоновом потоке.
  2. IntentService - подкласс сервиса, который последовательно выполняет запросы с помощью рабочего потока. Начиная с Android 8, использовать его не рекомендуется. Кроме того, IntentService устарел с Android 11.
  3. JobIntentService - замена IntentService. Вместо сервиса он использует JobScheduler для выполнения заданий. В более ранних версиях, чем Android 8, он будет действовать так же, как IntentService. Для получения дополнительной информации прочтите официальную документацию JobIntentService.
  4. Фоновая работа с WorkManager - это общая концепция фоновой работы в Android. Используйте его, когда вы захотите выполнять периодические процессы в будущем или когда у вас есть некоторые ограничения в работе приложения.

Объяснение ограничений для фонового выполнения

Android 8 и более новые версии имеют ограничения при использовании Android Services в фоновом потоке:

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

Дополнительные сведения см. В документации по ограничениям фонового выполнения.

Заметка

Из-за всех этих ограничений фоновые сервисы используются крайне редко. Как упоминалось выше, а также рекомендовано официальной документацией Android, лучшим решением для фоновой работы является использование Work Manager. Подробнее об этом читайте в разделе «Планирование задач с помощью Android WorkManager».

А теперь пора узнать о последнем виде сервисов.

Создание Bound Service

Bound Service - это все еще применение Service . Это компонент, с которым связываются другие компоненты, взаимодействуют с ним и осуществляют межпроцессное взаимодействие (IPC). Связанный компонент может быть Activity, другим сервисом, приемником трансляции или поставщиком контента.

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

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

Чтобы попробовать это, давайте добавим в Memo возможность воспроизводить звук.

Преобразование MusicService в реальный Service

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

Откройте MusicService.kt и расширьте его Service :

class MusicService : Service() 

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

override fun onBind(intent: Intent?): IBinder = binder

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

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

Сейчас Android Studio выдает ошибку, потому что мы не определили переменную binder . Чтобы исправить это, добавьте в начало класса следующее:

private val binder by lazy

Здесь мы выполняем отложенную инициализацию binder , который является разновидностью MusicBinder . Внедрение MusicBinder - ваш следующий шаг.

Определение Binder

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

Теперь, чтобы определить интерфейс, добавьте эти строки внизу класса:

inner class MusicBinder : Binder()

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

Определение методов сервисов для управления аудио

Этот сервис позволяет пользователю взаимодействовать со звуковыми иконками для воспроизведения, приостановки, остановки и перемешивания музыки. Для простоты, некоторые методы управления звуком уже определены. Однако сам музыкальный проигрыватель все еще не определен.

Чтобы создать музыкальный проигрыватель, добавьте этот код в initializeMediaPlayer() внутри MusicService.kt:

 musicMediaPlayer = MediaPlayer.create(this, randomSongs.first()).apply

Здесь вы используете MediaPlayer для запуска непрерывно повторяющегося звука первой песни в randomSongs .

Еще одна интересная особенность такого сервиса заключается в том, что он может предоставить имя воспроизводимого трека. Внутри MusicService добавьте:

fun getNameOfSong(): String = resources.getResourceEntryName(randomSongs.first()) .replaceFirstChar < if (it.isLowerCase()) it.titlecase(Locale.ENGLISH) else it.toString() >.replace("_", " ")

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

Все сделано! Теперь воспользуемся этими методами из другого компонента.

Создание колбэка для подключения к службе

Чтобы использовать MusicService внутри других классов, нам нужно создать его экземпляр. Откройте MainActivity.kt и добавьте в начало класса следующее:

private var musicService: MusicService? = null

Здесь мы объявляем переменную, которая содержит экземпляр сервиса. В данный момент сервис является null . Мы назначим новое значение, когда активити подключится к службе с помощью интерфейса Binder . Чтобы уловить изменения состояния соединения, создайте колбэк соединения.

Добавьте код под инициализацией musicService :

// 1 private val boundServiceConnection = object : ServiceConnection < // 2 override fun onServiceConnected(className: ComponentName, service: IBinder) < val binder: MusicService.MusicBinder = service as MusicService.MusicBinder musicService = binder.getService() mainViewModel.isMusicServiceBound = true >// 3 override fun onServiceDisconnected(arg0: ComponentName) < musicService?.runAction(MusicState.STOP) musicService = null mainViewModel.isMusicServiceBound = false >>

Этот блок кода не сложно понять:

  1. Это колбэк для состояния подключения сервиса.
  2. Когда активити подключается к сервису, система использует экземпляр MusicBinder и getService() чтобы передать ссылку в musicService .
  3. Когда сервис отключается, воспроизведение звука прекратится, если ссылка на сервис еще не приняла значение null , при этом ссылка на сервис будет удалена.

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

Привязка к Сервису

Следующим шагом мы создадим привязку сервиса при запуске MainActivity . Найдите onStart() и добавьте:

if (!mainViewModel.isMusicServiceBound) bindToMusicService()

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

Метод привязки еще не существует, поэтому добавьте его код ниже onDestroy() :

private fun bindToMusicService() < // 1 Intent(this, MusicService::class.java).also < // 2 bindService(it, boundServiceConnection, Context.BIND_AUTO_CREATE) >>

В этом куске кода мы:

  1. Заявляем о намерении запустить MusicService
  2. Предоставляем сервису информацию о нашем Intent вместе с колбэком соединения и флагом, который автоматически создает сервис, если привязка существует.

Чтобы избежать утечек памяти или ошибок, вам нужно добавить код для отмены привязки сервиса. Добавьте в unbindMusicService() следующее:

unbindService(boundServiceConnection)

Таким образом, вы говорите сервису выполнить onServiceDisconnected() в колбеке boundServiceConnection .

Использование методов сервисов

Как только сервис отключится, нам нужно остановить воспроизведение музыки. Добавьте следующий код над только что добавленной строкой unbindService() :

musicService?.runAction(MusicState.STOP)

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

Чтобы запустить звук, используем sendCommandToBoundService() . Теперь создадим общий вызов к изменению действия, чтобы уменьшить количество строк кода. Добавьте эту строку в sendCommandToBoundService() :

musicService?.runAction(state)

Таким образом, вы задаете сервису действие, которое он выполняет в соответствии с параметром sendCommandToBoundService() .

Осталось сделать еще кое- что! Как только саундтрек начнет воспроизводиться, сервис может предоставить информацию о названии песни. Перед тем, как это применить это в нашем приложении, нужно исправить момент с получением результата.

Внутри getNameOfSong() , замените строку, возвращающую текст «Unknown», на:

musicService?.getNameOfSong() ?: getString(R.string.unknown)

Здесь мы вызываем метод сервиса, который проверяет, какая звуковая дорожка в настоящее время воспроизводится, и возвращает необязательный результат String . Если результат равен null , вместо информации, полученной от сервиса, мы используем текст, взятый из ресурсов.

Запустите проект, затем нажмите значок «Play», чтобы запустить воспроизведение. Нажмите GET SONG NAME, чтобы увидеть название текущей песни во всплывающем сообщении. Наконец, вы можете наслаждаться музыкой во время игры!

Межпроцессные взаимодействия

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

Исходя из этого, Android позволяет запускать компоненты в другом процессе, который не используется для запуска приложения. Для этого вам нужно использовать тег процесса внутри AndroidManifest.xml. У процесса может быть случайное имя, например myNewProcess.

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

Вам нужно включить только один программный интерфейс - IBinder, но Android предоставляет три способа его определения:

  • Расширение подшивки
  • Использование мессенджера
  • Использование AIDL

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

Что дальше?

Загрузите конечный проект, если что-то у вас не получилось.

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

Android 12 внесет некоторые изменения в уведомления переднего плана. Ознакомиться с ними можно в официальной документации по Foreground Services.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *