본문 바로가기

Android(Kotlin) Study A부터 Z까지 등반

안드로이드 서비스 + Notification(음악플레이어 컨트롤)

음악 플레이어 및 다음 노래, 노티에 타이틀 제목 표시를 예제로 해보겠습니다.

 

 

 


<service
    android:name=".MyMusicPlayerService"
    android:enabled="true"
    android:exported="true" />

노티피케이션에서 보낼 액션 + foreground 요청액션 지정

package com.example.mediabrowserserviceexample

object Actions {
    private const val prefix = "com.example.mediabrowserserviceexample.action."
    const val MAIN = prefix + "main"
    const val PREV = prefix + "prev"
    const val NEXT = prefix + "next"
    const val PLAY = prefix + "play"
    const val START_FOREGROUND = prefix + "startforeground"
    const val STOP_FOREGROUND = prefix + "stopforeground"
}

포어그라운드니깐 노티를 띄워줘야하겠지? 우린 그래서 그걸로 플레이어 컨트롤바를 만들 것이다. Prev, Next 버튼 클릭시 다음 노래 재생 해줌과 동시에 노티에 해당 노래 제목을 바꿔줄 것이다.

package com.example.mediabrowserserviceexample

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat

object MusicNotification {
    const val CHANNEL_ID = "foreground_service_channel" // 임의의 채널 ID
    fun createNotification(
        context: Context, title: String //노래 제목이다.
    ): Notification {
        // 알림 클릭시 MainActivity로 이동됨
        val notificationIntent = Intent(context, MainActivity::class.java)
        notificationIntent.action= Actions.MAIN
        notificationIntent.flags= Intent.FLAG_ACTIVITY_NEW_TASKor
                Intent.FLAG_ACTIVITY_CLEAR_TASK

val pendingIntent = PendingIntent
            .getActivity(context, 0, notificationIntent,FLAG_UPDATE_CURRENT)

        // 각 버튼들에 관한 Intent
        val prevIntent = Intent(context, MyMusicPlayerService::class.java)
        prevIntent.action= Actions.PREV
        val prevPendingIntent = PendingIntent
            .getService(context, 0, prevIntent, 0)

        val playIntent = Intent(context, MyMusicPlayerService::class.java)
        playIntent.action= Actions.PLAY
        val playPendingIntent = PendingIntent
            .getService(context, 0, playIntent, 0)

        val nextIntent = Intent(context, MyMusicPlayerService::class.java)
        nextIntent.action= Actions.NEXT
        val nextPendingIntent = PendingIntent
            .getService(context, 0, nextIntent, 0)

        // 알림
        val notification = NotificationCompat.Builder(context, CHANNEL_ID)
            .setContentTitle("Music Player")
            .setContentText("My Music $title")
            .setSmallIcon(R.drawable.ic_launcher_background)
            .setOngoing(true) // true 일경우 알림 리스트에서 클릭하거나 좌우로 드래그해도 사라지지 않음
            .addAction(
                NotificationCompat.Action(
                    android.R.drawable.ic_media_previous,
                    "Prev", prevPendingIntent
                )
            )
            .addAction(
                NotificationCompat.Action(
                    android.R.drawable.ic_media_play,
                    "Play", playPendingIntent
                )
            )
            .addAction(
                NotificationCompat.Action(
                    android.R.drawable.ic_media_next,
                    "Next", nextPendingIntent
                )
            )
            .setContentIntent(pendingIntent)
            .build()

        // Oreo 부터는 Notification Channel을 만들어야 함
        if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) {
            val serviceChannel = NotificationChannel(
                CHANNEL_ID, "Music Player Channel", // 채널표시명
                NotificationManager.IMPORTANCE_DEFAULT
)
            val manager = context.getSystemService(NotificationManager::class.java)
            manager?.createNotificationChannel(serviceChannel)
        }
        return notification
    }
}

MyMusicPlayerService.kt

주요 함수들부터 설명하겠음.

onCreate() 초기에 포어그라운드로 실행되기 전에 미리 음악을 켜준다.

override fun onCreate() {
    super.onCreate()
    mediaPlayer = MediaPlayer.create(this, musicList[position % 3]);
    Log.e("TAG", "onCreate()")
}

노티와 음악에 대한 변수들은 다음과 같다.

val musicList =mutableListOf(R.raw.samples, R.raw.sample_2, R.raw.sample_3)
val musicTitle =listOf("one", "two", "three")
var position = 0
var mediaPlayer: MediaPlayer? = null
var notification: Notification? = null

companion object {
        const val NOTIFICATION_ID = 20
    }

포어그라운드 실행 및 중지 함수는 다음과 같이 선언해두었다.

private fun startForegroundService() {
    notification = MusicNotification.createNotification(this, musicTitle[position%3])
    startForeground(NOTIFICATION_ID, notification)
}

private fun stopForegroundService() {
    stopForeground(true)
    stopSelf()
}

onStartCommand()로 시작된 서비스로 구현하였으며, 노티피케이션과 액티비티에서 오는 액션에 따라서 when 구문으로 분기를 나누었다.

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    Log.e("TAG", "Action Received = ${intent?.action}")
    // intent가 시스템에 의해 재생성되었을때 null값이므로 Java에서는 null check 필수
    when (intent?.action) {
        Actions.START_FOREGROUND -> {
            Log.e("TAG", "Start Foreground 인텐트를 받음")
            startForegroundService()
        }
        Actions.STOP_FOREGROUND -> {
            Log.e("TAG", "Stop Foreground 인텐트를 받음")
            stopForegroundService()
        }
        Actions.PREV -> {
            mediaPlayer?.stop()
            mediaPlayer?.release()
            mediaPlayer = MediaPlayer.create(this, musicList[--position % 3])
            notification = MusicNotification.createNotification(this, musicTitle[position%3])
            startForeground(NOTIFICATION_ID, notification) //노티 타이틀 변경을 위해 재 생성
        }
        Actions.PLAY -> {
                mediaPlayer!!.start()
        }
        Actions.NEXT -> {
            mediaPlayer?.stop()
            mediaPlayer?.release()
            mediaPlayer = MediaPlayer.create(this, musicList[++position % 3])
            notification = MusicNotification.createNotification(this, musicTitle[position%3])
            startForeground(NOTIFICATION_ID, notification)
//노티 타이틀 변경을 위해 재 생성
        }
    }
    mediaPlayer?.start()
    return START_STICKY
}

MainActivity.kt

💡 사실상 별게 없다. 해당 서비스를 띄우고 재생, 정지만 서비스에게 알려주고 서비스를 띄워주는 역할을 하고 있다.

 

onCreate()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityMainBinding.inflate(layoutInflater)
    binding.btnPlay.setOnClickListener{
val intent = Intent(this@MainActivity, MyMusicPlayerService::class.java)
        intent.action= Actions.START_FOREGROUND
        startService(intent)
}
binding.btnStop.setOnClickListener{
val intent = Intent(this@MainActivity, MyMusicPlayerService::class.java)
        intent.action= Actions.STOP_FOREGROUND
        startService(intent)
}
setContentView(binding.root)
}

실행화면

💡 Move는 신경 안써도 된다.

재생시 노티

 

Next 눌렀을 때 노티, ContentText를 변경하게 했다.