
val mPlayer: MediaPlayer? = MediaPlayer.create(this, R.raw.sample)
mPlayer?.start()
val mtUri: Uri = ... // URI 초기화
val mediaPlayer: MediaPlayer? = MediaPlayer().apply {
setAudioStreamType(AudioManager.STREAM_MUSIC)
setDataSource(applicationCOntext, myUri)
prepare()
start()
}
생명 주기
1. startService() 함수를 이용해 서비스 시작
2. 서비스가 시작되면 서비스 내의 콜백 메서드인 onCreate()와 onStartCommand()가 차례로 호출되어 시작된 상태가 됨
3. 시작된 서비스는 stopSelf() 함수로 중지하거나, 다른 구성요소가 stopService()를 호출해 서비스를 완전히 종료시키기 전까지는 계속 실행중인 상태임
4. 서비스가 종료되면 onDestroy()함수가 호출되어 서비스가 완전히 종료
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import androidx.appcompat.app.AppCompatActivity
import android.os.IBinder
class MainActivity : AppCompatActivity() {
val mServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
// 서비스 연결 성공
}
override fun onServiceDisconnected(name: ComponentName?) {
// 서비스 연결 실패
}
}
private fun bindService() {
val intent = Intent(this, AudioPlayerService::class.java)
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
}
}
생명 주기
다음은 MusicPlayerService.kt 내 startForegroundService() 함수
fun startForegroundService() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager val mChannel = NotificationChannel( // 알림 채널을 생성 "CHANNEL_ID", "CHANNEL_NAME", NotificationManager.IMPORTANCE_DEFAULT ) notificationManager.createNotificationChannel(mChannel) } // 알림 생성 val notification: Notification = Notification.Builder(this, "CHANNEL_ID") .setSmallIcon(R.drawable.ic_play) // 알림 아이콘 .setContentTitle("뮤직 플레이어 앱") // 알림의 제목을 설정 .setContentText("앱이 실행 중입니다.") // 알림의 내용을 설정 .build() startForeground(1, notification) // startForeground(알림 id, 알림 내용) }
다음은 MusicPlayerService.kt 내 onStartCommand() 함수
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { return START_STICKY }
인수1: 어떤 서비스와 바인드 할건지
인수2: 구현한대로 연결되면 onServiceConnected()를 실행하고 연결이 끊기면 onServiceDisconnected()를 실행
인수3: 바인드할 시점에 서비스가 실행되지 않은 상태라면 서비스를 생성


<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<service android:name="com.example.mediaplayerapp.MusicPlayerService"/>
MainActivity.kt
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
class MainActivity : AppCompatActivity(), View.OnClickListener {
lateinit var btn_start : Button
lateinit var btn_pause : Button
lateinit var btn_stop : Button
var mService: MusicPlayerService? = null // 서비스 변수
// 서비스 구성요소 연결 상태 모니터링
val mServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
// MusicPlayerBinder로 형변환 해줌
mService = (service as MusicPlayerService.MusicPlayerBinder).getService()
}
override fun onServiceDisconnected(name: ComponentName?) {
// 서비스가 끊기면 mService를 null로 만듦
mService = null
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_start = findViewById(R.id.btn_start)
btn_pause = findViewById(R.id.btn_pause)
btn_stop = findViewById(R.id.btn_stop)
btn_start.setOnClickListener(this)
btn_pause.setOnClickListener(this)
btn_stop.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id) {
R.id.btn_start -> {
play()
}
R.id.btn_pause -> {
pause()
}
R.id.btn_stop -> {
stop()
}
}
}
override fun onResume() {
// 액티비티가 사용자에게 보일 때마다 실행되는 콜백 함수
super.onResume()
// 서비스 실행 처리
if (mService == null) { // 아직 서비스가 액티비티와 연결되지 않았을 때
// 안드로이드O 이상이면 startForegroundService 사용
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(Intent(this, MusicPlayerService::class.java))
} else {
startService(Intent(applicationContext, MusicPlayerService::class.java))
}
// 액티비티를 서비스와 바인드시킴
val intent = Intent(this, MusicPlayerService::class.java)
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
}
}
override fun onPause() {
super.onPause()
// 사용자가 액티비티를 떠났을 때 처리
if (mService != null) { if (!mService!!.isPlaying()) { // mService가 재생되고 있지 않다면
mService!!.stopSelf() // 서비스 중단
}
unbindService(mServiceConnection) // 서비스로부터 연결 끊기
mService = null
}
}
private fun play() {
mService?.play()
}
private fun pause() {
mService?.pause()
}
private fun stop() {
mService?.stop()
}
}
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/btn_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/pink"
android:text="@string/start"
app:layout_constraintBottom_toTopOf="@+id/btn_pause"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/purple"
android:text="@string/pause"
app:layout_constraintBottom_toTopOf="@+id/btn_stop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_start" />
<Button
android:id="@+id/btn_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/skyBlue"
android:text="@string/stop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_pause" />
</androidx.constraintlayout.widget.ConstraintLayout>
MusicPlayerService.kt
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.media.MediaPlayer
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.widget.Toast
import androidx.annotation.RequiresApi
class MusicPlayerService : Service() {
var mMediaPlayer: MediaPlayer? = null // 미디어 플레이어 객체를 null로 초기화
var mBinder: MusicPlayerBinder = MusicPlayerBinder()
inner class MusicPlayerBinder : Binder() {
fun getService(): MusicPlayerService {
return this@MusicPlayerService
}
}
// 서비스를 startService()로 생성하든, bindService()로 생성하든 onCreate()는 처음에 한 번만 실행
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate() {
super.onCreate()
// 앱이 실행되고 있다는 알림 생성
startForegroundService() // 포그라운드 서비스 시작
}
// 바인드
override fun onBind(intent: Intent?): IBinder {
return mBinder
}
// 시작된 상태 (백그라운드)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
// 서비스 종료
override fun onDestroy() {
super.onDestroy()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
stopForeground(true)
}
}
@SuppressLint("ForegroundServiceType")
@RequiresApi(Build.VERSION_CODES.O)
fun startForegroundService() {
// 안드로이드O 부터는 반드시 알림 채널을 사용하여 사용자에게 알림을 보여줘야 함
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
// 알림 채널: 알림을 용도나 중요도에 따라 구분하여 사용자가 앱의 알림을 관리할 수 있게 함
val mChannel = NotificationChannel( // 알림 채널을 생성
"CHANNEL_ID",
"CHANNEL_NAME",
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(mChannel)
}
// 알림 생성
val notification: Notification = Notification.Builder(this, "CHANNEL_ID")
.setSmallIcon(R.drawable.ic_play) // 알림 아이콘
.setContentTitle("뮤직 플레이어 앱") // 알림의 제목을 설정
.setContentText("앱이 실행 중입니다.") // 알림의 내용을 설정
.build()
startForeground(1, notification) // startForeground(알림 id, 알림 내용)
}
fun isPlaying() : Boolean{
return (mMediaPlayer != null && mMediaPlayer?.isPlaying ?: false)
}
fun play() {
if (mMediaPlayer == null) { // 음악이 재생 중이지 않을 때
// 음악 파일의 리소스를 가져와 미디어 플레이어 객체를 할당
mMediaPlayer = MediaPlayer.create(this, R.raw.sample)
mMediaPlayer?.setVolume(1.0f, 1.0f) // 볼륨 지정
mMediaPlayer?.isLooping = true // 반복재생 여부
mMediaPlayer?.start() // 음악 재생
} else { // 음악이 재생 중일 때
if (mMediaPlayer!!.isPlaying) {
Toast.makeText(this, "이미 음악이 실행 중입니다.", Toast.LENGTH_SHORT).show()
} else {
mMediaPlayer?.start() // 음악 재생
}
}
}
fun pause() {
mMediaPlayer?.let {
if (it.isPlaying) {
it.pause()
}
}
}
fun stop() {
mMediaPlayer?.let {
if(it.isPlaying) {
it.stop()
it.release()
mMediaPlayer = null
}
}
}
}
