В ходе повседневного использования наших устройств 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 здесь.