В ходе повседневного использования наших устройств Android могут быть моменты, когда нам может понадобиться приложение для продолжения работы, даже если мы не используем его напрямую. Например, мы можем захотеть слушать музыку из музыкального проигрывателя, когда мы просматриваем веб-страницы или отвечаем на сообщения в социальных сетях, пока загружается обновление для одной из наших мобильных игр.
Однако есть одна проблема: если задача (например, воспроизведение музыки), которую запускает приложение, находится в действии, каждый раз, когда мы существуем в приложении или переключаемся на другое приложение и т. д., текущая запущенная задача останавливается. (подробнее об этом здесь). Здесь на помощь приходит компонент Android Services.
Служба — это компонент приложения для Android, который работает в фоновом режиме. Он не предоставляет пользовательский интерфейс, но вместо этого может управляться через привязку к другому компоненту (т.е. активности). После запуска служба будет постоянно работать в фоновом режиме, даже если пользователь покинет приложение или произойдет изменение конфигурации.
Существуют различные виды услуг:
- Фоновые службы — служба, не замечаемая пользователем. Эта служба продолжает работать, даже если пользователь существует приложение
- Foreground Services — этот тип службы используется для выполнения операций, которые видны пользователю (например, воспроизведение музыки). Он будет работать, даже если пользователь закроет приложение, пока его задача не будет завершена.
- Bounded Services — служба, подключенная к другому компоненту, который может использовать свои общедоступные методы.
В этом уроке я покажу вам, как использовать службы Android, для этого мы создадим две службы: фоновую службу и службу переднего плана. Фоновая служба будет состоять из метода извлечения и воспроизведения текущей мелодии звонка устройства и метода ее остановки. Мы также привяжемся к этому сервису, чтобы им можно было управлять из основного действия. Служба переднего плана покажет уведомление, которое будет состоять из кнопки действия, которая останавливает службу (с помощью BroadcastReceiver. Основное действие будет состоять из кнопок для запуска обеих соответствующих служб и кнопки для привязки к фону). Он также будет состоять из двух кнопок для вызова методов остановки и воспроизведения фоновой службы соответственно.
Примечание. Мы также можем привязываться к службе переднего плана, а также создавать в ней методы, но здесь этого делать не будем.
Шаг 1: Подготовка
- Загрузите Android Studio и создайте новый проект.
- Выберите Пустая активность.
- В окне Configure Your Project назовите свой проект «Android Service» (без кавычек) и нажмите «Готово».
Шаг 2. Настройте Activity_main.xml
Перейдите к файлу activity_main.xml и добавьте в него этот код.
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/background_service" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:layout_marginBottom="8dp" android:text="BackGroundService" app:layout_constraintBottom_toTopOf="@+id/bound_service" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" /> <Button android:id="@+id/bound_service" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:text="Bound Service" app:layout_constraintBottom_toTopOf="@+id/foreground_service" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/background_service" /> <Button android:id="@+id/foreground_service" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="32dp" android:text="Foreground Service" app:layout_constraintBottom_toTopOf="@+id/play_button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/bound_service" /> <Button android:id="@+id/play_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="32dp" android:text="Play" app:layout_constraintBottom_toTopOf="@+id/stop_button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/foreground_service" /> <Button android:id="@+id/stop_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="35dp" android:text="Stop" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/play_button" /> </androidx.constraintlayout.widget.ConstraintLayout>
Шаг 3. Создайте службы
- Затем перейдите в окно проекта.
- Щелкните правой кнопкой мыши пакет com.example.androidservice.
- Выберите новая › служба›услуга.
- В окне Новый компонент Android назовите службу «MyBackGroundService» (без кавычек) и нажмите «Готово».
- Повторите шаги, описанные выше, но на этот раз назовите службу «MyForeGroundService» (без кавычек).
Шаг 4. Настройте основное действие
Игнорируя пока классы обслуживания, перейдите к классу Main Activity и добавьте этот код:
package com.example.androidservice import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.os.Build import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.IBinder import android.view.View import android.widget.Button import android.widget.Toast class MainActivity : AppCompatActivity(), View.OnClickListener { private lateinit var myBackGroundService: MyBackGroundService private lateinit var backGroundServiceIntent: Intent private lateinit var backgroundServiceBtn: Button private lateinit var boundServiceBtn: Button private lateinit var foregroundServiceBtn: Button private lateinit var playBtn: Button private lateinit var stopBtn: Button private var isBackGroundServiceStarted = false private var isForeGroundServiceStarted = false private var mBound = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) backgroundServiceBtn = findViewById(R.id.background_service) backgroundServiceBtn.setOnClickListener(this) boundServiceBtn = findViewById(R.id.bound_service) boundServiceBtn.setOnClickListener(this) foregroundServiceBtn = findViewById(R.id.foreground_service) foregroundServiceBtn.setOnClickListener(this) playBtn = findViewById(R.id.play_button) playBtn.setOnClickListener(this) stopBtn = findViewById(R.id.stop_button) stopBtn.setOnClickListener(this) backGroundServiceIntent = Intent(this, MyBackGroundService::class.java) } override fun onClick(v: View?) { if (v != null) { when (v.id) { R.id.background_service -> startMyBackgroundService() R.id.bound_service -> bindToMyService() R.id.foreground_service -> startMyForegroundService() R.id.stop_button -> stop() R.id.play_button -> play() } } } /*Used to start the background service. if the foreground service is running it will stop it.*/ private fun startMyBackgroundService() { if (isForeGroundServiceStarted) { stopService(backGroundServiceIntent) } startService(backGroundServiceIntent) isBackGroundServiceStarted = true } // binds to the service. private fun bindToMyService() { bindService(backGroundServiceIntent, connection, Context.BIND_AUTO_CREATE) } /** Defines callbacks for service binding, passed to bindService() */ private val connection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // We've bound to MyService, cast the IBinder and get MyService instance val binder = service as MyBackGroundService.MyBackGroundBinder myBackGroundService = binder.getService() mBound = true } override fun onServiceDisconnected(arg0: ComponentName) { mBound = false } } /** Used to start the foreGround service. if the backGround service is running it will stop it and unbind if bounded.*/ private fun startMyForegroundService() { if (mBound) { myBackGroundService.stopRingTone() unbindService(connection) } if (isBackGroundServiceStarted) { stopService(backGroundServiceIntent) } val foreGroundServiceIntent = Intent(this, MyForeGroundService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { applicationContext.startForegroundService(foreGroundServiceIntent) } else { applicationContext.startService(foreGroundServiceIntent) } isForeGroundServiceStarted = true } // stops the ringtone from playing in the service. private fun stop() { if (mBound) { myBackGroundService.stopRingTone() } else { Toast.makeText(this, "service not yet bound", Toast.LENGTH_SHORT).show() } } // plays the ringtone in the service. private fun play() { if (mBound) { myBackGroundService.playRingTone() } else { Toast.makeText(this, "service not yet bound", Toast.LENGTH_SHORT).show() } } }
Шаг 5. Настройка служб
Сначала перейдите в класс MyBackGroundService и добавьте в него этот код:
package com.example.androidservice import android.app.Service import android.content.Intent import android.media.RingtoneManager import android.net.Uri import android.os.Binder import android.os.IBinder import android.widget.Toast class MyBackGroundService : Service() { // Binder given to clients private val binder = MyBinder() private var play: Boolean = false private lateinit var uri: Uri override fun onBind(intent: Intent?): IBinder { Toast.makeText(this, "Service has been bound", Toast.LENGTH_SHORT).show() return binder } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { playRingTone() return super.onStartCommand(intent, flags, startId) } // A method that gets the current ringtone of the device and plays it. fun playRingTone() { play = true uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE) val ringTone = RingtoneManager.getRingtone(applicationContext, uri) Thread { while (play) { ringTone.play() Thread.sleep(2000) } }.start() } // stops the ringtone that is playing. fun stopRingTone() { play = false } inner class MyBinder : Binder() { // Return this instance of LocalService so clients can call public methods fun getService(): MyBackGroundService = this@MyBackGroundService } }
Затем перейдите в класс MyForeGroundService и добавьте этот код:
package com.example.androidservice import android.app.* import android.content.Context import android.content.Intent import android.os.Build import android.os.IBinder import androidx.core.app.NotificationCompat class MyForeGroundService : Service() { private val NOTIFICATION_CHANNEL_ID = "Services Notification" private val ONGOING_NOTIFICATION_ID = 1 private val ACTION_STOP_LISTEN = "action_stop_listen" private lateinit var broadCastPendingIntent: PendingIntent private lateinit var notificationPendingIntent: PendingIntent override fun onBind(intent: Intent): IBinder { TODO() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if ((intent!!.action != null) && (intent.action == ACTION_STOP_LISTEN)) { stopForeground(true) stopSelf() } else { createNotificationChannel() createPendingIntents() startForeground(ONGOING_NOTIFICATION_ID, createNotification()) } return super.onStartCommand(intent, flags, startId) } private fun createNotificationChannel() { // Create the NotificationChannel, but only on API 26+ because // the NotificationChannel class is new and not in the support library if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val name = "My Notif" val descriptionText = "Notif Descript" val importance = NotificationManager.IMPORTANCE_DEFAULT val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance).apply { description = descriptionText } // Register the channel with the system val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } } private fun createPendingIntents() { // If the notification supports a direct reply action, use // PendingIntent.FLAG_MUTABLE instead. notificationPendingIntent = Intent(this, MainActivity::class.java).let { notificationIntent -> PendingIntent.getActivity( this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE ) } broadCastPendingIntent = Intent(this, MyBroadCastReceiver::class.java).putExtra( "Action", ACTION_STOP_LISTEN ).let { stopForeGroundIntent -> PendingIntent.getBroadcast( this, 0, stopForeGroundIntent, PendingIntent.FLAG_IMMUTABLE ) } } private fun createNotificationAction(): NotificationCompat.Action { return NotificationCompat.Action( R.drawable.ic_launcher_background, "Stop", broadCastPendingIntent ) } private fun createNotification(): Notification { return NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) .setContentTitle("Foreground Service") .setContentText("ringtone is playing") .setSmallIcon(R.drawable.ic_launcher_background) .setContentIntent(notificationPendingIntent) .setTicker("Music Playing") .setAutoCancel(true) .addAction(createNotificationAction()) .build() } }
Шаг 6: Создайте BroadcastReceiver
Теперь вам нужно создать BroadReceiver для ответа на нажатие кнопки действия службы:
- Сначала перейдите в файл манифеста и добавьте в него компонент приемника следующим образом:
<receiver android:name=".MyBroadCastReceiver"></receiver>
будет показана ошибка, но это потому, что мы еще не создали класс.
- Перейдите в окно проекта.
- Щелкните правой кнопкой мыши пакет com.example.androidservice.
- Выберите создать › класс/файл Kotlin.
- В окне Новый класс/файл Kotlin назовите службу «MyBackGroundService» (без кавычек) и нажмите «Готово».
- Настройте класс следующим образом:
package com.example.androidservice import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.widget.Toast class MyBroadCastReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { Toast.makeText(context, "Broadcast Started", Toast.LENGTH_SHORT).show() val foreGroundServiceIntent = Intent(context, MyForeGroundService::class.java) context?.startForegroundService( foreGroundServiceIntent.setAction( intent?.getStringExtra( "Action" ) ) ) } } }
Шаг 7. Запустите приложение
Запустите приложение на своем устройстве или эмуляторе и все.
Все готово
Вот и все, это были лишь некоторые из основных вещей, которые вы можете делать с сервисами Android. Чтобы узнать больше об этом, перейдите на официальный сайт документации Android здесь.