2024. 5. 14. 17:28, ๐ฑAndroid Study
๋ฐ์ํ
์ด๋ฒ์ Service๋ฅผ ์ดํดํ๊ธฐ ์ํ ๋ฎค์ง ํ๋ ์ด์ด ์ฑ์ ๋ง๋ค์ด ๋ณด์๋ค. ์ญ์ Joyce๋์ ์์ ์ ์ฐธ๊ณ ํ์๋ค.
๐ก ์์ ์ฌ์ ๋ฐฉ๋ฒ
(1) Raw ๋ฆฌ์์ค๋ฅผ ์ด์ฉํ์ฌ ์ฌ์ (res/raw ํด๋์ ์์ ํ์ผ์ ์ง์ ์ฝ์ )
val mPlayer : MediaPlayer? = MediaPlayer.create(this, R.raw.FILE_NAME)
mPlayer?.start()
(2) URI๋ฅผ ์ฌ์ฉํด ์ฌ์ (๊ธฐ๊ธฐ ์์ ์๋ ์ค๋์ค ํ์ผ์ ์์น URI ๊ฐ์ผ๋ก ์ฌ์)
val myUri: Uri = ... // URI ์ด๊ธฐํ
val mediaPlayer: MediaPlayer? = MediaPlayer().apply {
setAudioStreamType(AudioManager.STREAM_MUSIC)
setDataSource(applicationContextm, myUri)
prepare()
start()
}
๐ก MediaPlayer ํด๋์ค ์ง์ ํจ์
- ํ์ผ ์ค๋น
- prepare() : ๋ฉ์ธ ์ค๋ ๋์์ ์คํ, ์ฉ๋ ํด ๊ฒฝ์ฐ ANR ๊ฐ๋ฅ์ฑ
- prepareAsync() : ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋์์ ์คํ, ANR(์๋ต์์) ๋ฐ์ํ์ง ์์, onPreparedListener ์ฌ์ฉ ๊ฐ๋ฅ
- setDataResource() - ํ์ผ ์ฌ์
- start() : ์ฌ์
- pause() : ์ผ์๋ฉ์ถค - ํ์ผ ๋ฉ์ถค
- reset() : ํ์ฌ ์ฌ์๋๋ ๋ฏธ๋์ด ๋ฉ์ถค ๋ฐ MediaPlayer ๊ฐ์ฒด ์ด๊ธฐํ - ์์
๊ธธ์ด ์ฐพ๊ธฐ
- getDuration() : ์์ ์ ๊ธธ์ด, ๋จ์๋ ๋ฐ๋ฆฌ์ด(ms)๋ก ๋ฐํ - ํน์ ๊ตฌ๊ฐ ์ด๋
- seekTo() - ์์ ํด์
- release() : ์ฌ์ฉํ๋ ๋ฉ๋ชจ๋ฆฌ์ ์์ ํด์ . MediaPlayer๋ฅผ ๋ ์ด์ ์ฌ์ฉํ์ง ์๋๋ค๋ฉด ๋ฐ๋์ release() ํธ์ถ
๐ก ์๋น์ค์ ์๋ช ์ฃผ๊ธฐ
์๋๋ก์ด๋ 4๋ ๊ตฌ์ฑ์์. ์๋น์ค๋ ๋
๋ฆฝ๋ ๊ตฌ์ฑ์์์ด๋ฏ๋ก ๋
๋ฆฝ๋ ์๋ช
์ฃผ๊ธฐ๋ฅผ ๊ฐ์ง (์กํฐ๋นํฐ๊ฐ ์๋ฉธ๋์ด๋ ์คํ์ค)
ํฌ๊ฒ ์์๋ ์๋น์ค์ ๋ฐ์ธ๋๋ ์๋น์ค ๋ ๊ฐ๋ก ๋๋๋ฉฐ, ๋ณดํต์ ์ด ๋ ๊ฐ๋ฅผ ๊ฐ์ด ๊ฐ๊ณ ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์
(1) startService()
- ์กํฐ๋นํฐ๋ ๋ค๋ฅธ ์๋น์ค์์ startService() ๋ฅผ ํธ์ถํ๋ฉด ์๋น์ค ์์
- ์๋น์ค ๋ด์ ์ฝ๋ฐฑ ๋ฉ์๋์ธ onCreate(), onStartCommand()๊ฐ ํธ์ถ๋๋ฉฐ ์์๋ ์ํ๊ฐ ๋จ
- ํ ๋ฒ ์์๋ ์ํ์ ์๋น์ค๋ stopSelf() ํจ์๋ก ์์์ ์ค์งํ๊ฑฐ๋, ๋ค๋ฅธ ๊ตฌ์ฑ์์๊ฐ stopService()๋ฅผ ํธ์ถํ์ฌ ์ข ๋ฃํ๊ธฐ ์ ๊น์ง๋ ๊ณ์ ์คํ์ค์ธ ์ํ๋ก ์กด์ฌ
- stopSelf() ํน์ stopService()๋ก ์ข ๋ฃ๋ ๋ค onDestroy()๋ฅผ ๊ฑฐ์ณ ์๋น์ค ์ข ๋ฃ
- ์ฐธ๊ณ ๋ก ์๋ฌด๋ฆฌ ๋ง์ ๊ตฌ์ฑ์์์์ startService()๋ฅผ ํธ์ถํ๋๋ผ๋ ์๋น์ค ๊ฐ์ฒด ์์ฑ ๋ฐ onCreate() ํธ์ถ์ ์ฒ์ ํ ๋ฒ
(2) bindService()
- ๋ฐ์ธ๋๋ ์๋น์ค๋ ๋ค๋ฅธ ๊ตฌ์ฑ์์์ ์ฐ๊ฒฐ์ด ๋ ๋์ ์คํ๋๋ ์๋น์ค
- ๋ค๋ฅธ ๊ตฌ์ฑ์์๋ค์ ๋ฐ์ธ๋๋ ๋์์๋ง ์คํ๋๋ ๊ฒ์ด๊ณ , ๊ณ์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์คํ๋๋ ๊ฒ์ ์๋
(3) ๊ฒฐ๋ก
- ์์๋ ์๋น์ค๋ ๋ค๋ฅธ ๊ตฌ์ฑ์์์์ ์ฐ๊ฒฐ๊ณ ๋ฆฌ๊ฐ ์์ผ๋ฉฐ ํฌ๊ธฐ๊ฐ ํฐ ๋์์์ ๋ค์ด ๋ฐ๋ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด๋ณผ ์ ์์
- ๋ฐ์ธ๋๋ ์๋น์ค๋ ๋ค๋ฅธ ๊ตฌ์ฑ์์์ ์ํต์ ํ๋ฉฐ, ์ฐ๊ฒฐ์ ๋๊ฒ๋๋ฉด ์๋น์ค๊ฐ ์ข ๋ฃ๋๋ฏ๋ก ๊ณ์ ์ด์์์ด์ผ ํ๋ ํ์คํฌ์๋ ๋ถ์ ์ ํจ
- ๋ณดํต์ ๋ฐฑ๊ทธ๋ผ์ด๋์ ๋จ์์๋ ๋์์ ๋ค๋ฅธ ๊ตฌ์ฑ์์์ ์ฐ๊ฒฐ๋์ด ์ํต๋ ๊ฐ๋ฅํ๊ฒ๋ ๋ ๊ฐ๋ฅผ ๋์์ ์ฌ์ฉ
→ startService()์ bindService() ๋ ๋ค ์คํ
๐ก ํฌ๊ทธ๋ผ์ด๋์ ๋ฐฑ๊ทธ๋ผ์ด๋
- ํฌ๊ทธ๋ผ์ด๋๋ ์ํ ํ์์ค์ ์๋ฆผ์ด ํ์๋๋ฉฐ, ์ฌ์ฉ์๊ฐ ์๋น์ค๊ฐ ์คํ๋๊ณ ์์์ ๋ฅ๋์ ์ผ๋ก ์ธ์งํ ์ ์๋ ์๋น์ค
- ๋ฐฑ๊ทธ๋ผ์ด๋๋ ์ฌ์ฉ์๊ฐ ๋ณด์ด์ง ์๋ ๊ณณ์์ ์กฐ์ฉํ ์์ ์ํ
- ์๋๋ก์ด๋ API ๋ ๋ฒจ 26๋ถํฐ๋ ํฌ๊ทธ๋ผ์ด๋ ์๋น์ค๋ฅผ ํตํด ์ฌ์ฉ์์๊ฒ ์๋ฆฌ๊ฒ๋ ํจ
startForegroundService()
๐ก ๋งค๋ํ์คํธ์ ๊ถํ ๋ฐ ์๋น์ค ์ถ๊ฐ
<manifest>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> // ์ถ๊ฐ
<application
<service android:name=".MusicPlayerService"/> // ์ถ๊ฐ
</application>
</manifest>
๐ก ๊ธฐ๋ณธ ์๋น์ค ํ ๊ตฌํ
package com.limheejin.musicplayer
import android.app.Service
import android.content.Intent
import android.os.IBinder
class MusicPlayerService : Service() {
// onCreate() : startService()๋ก ์์ฑํ๋ bindService()๋ก ์์ฑํ๋ , ์๋น์ค๊ฐ ์์ฑ๋ ๋ ๋ฑ ํ ๋ฒ๋ง ์คํ
override fun onCreate() {
super.onCreate()
startForegroundService() // ํฌ๊ทธ๋ผ์ด๋ ์๋น์ค ์์
}
// ๋ฐ์ธ๋ : bindService()๋ฅผ ํธ์ถํ ๋ ์คํ๋๋ ํจ์, ๋ฐ์ธ๋๊ฐ ํ์์๋ค๋ฉด null์ ๋ฐํํ๋ฉด ๋จ
override fun onBind(intent: Intent?): IBinder? { // IBinder : ์๋น์ค์ ๊ตฌ์ฑ์์๋ฅผ ์ด์ด์ฃผ๋ ๋งค๊ฐ์ฒด ์ญํ
TODO("Not yet implemented")
}
// ์์๋ ์ํ & ๋ฐฑ๊ทธ๋ผ์ด๋ : startForegroundService() ํธ์ถํ ๋ ์คํ๋๋ ํจ์
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
// ์๋น์ค ์ข
๋ฃ : onCreate()์์ ์ํํ์์ค์ ๋ณด์ฌ์ฃผ์๋ ์๋ฆผ ํด์
override fun onDestroy() {
super.onDestroy()
}
// ๋ฏธ๋ฆฌ ํจ์ ๊ตฌ์ฑ ์์ฑํด๋๊ธฐ
fun startForegroundService() {}
fun isPlaying() {}
fun play() {}
fun pause() {}
fun stop() {}
}
๐ก ๊ตฌ์ฒด์ ์ธ ์๋น์ค ์์
package com.limheejin.musicplayer
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
class MusicPlayerService : Service() {
var mMediaPlayer: MediaPlayer? = null // ๋ฏธ๋์ด ํ๋ ์ด์ด ๊ฐ์ฒด๋ฅผ null๋ก ์ด๊ธฐํ
var mBinder: MusicPlayerBinder = MusicPlayerBinder()
inner class MusicPlayerBinder : Binder() { // ๋ฐ์ธ๋๋ฅผ ๋ฐํํ์ฌ ์๋น์ค ํจ์ ์ธ ์ ์๋๋ก
fun getService(): MusicPlayerService {
return this@MusicPlayerService
}
}
// onCreate() : startService()๋ก ์์ฑํ๋ bindService()๋ก ์์ฑํ๋ , ์๋น์ค๊ฐ ์์ฑ๋ ๋ ๋ฑ ํ ๋ฒ๋ง ์คํ
override fun onCreate() {
super.onCreate()
startForegroundService() // ํฌ๊ทธ๋ผ์ด๋ ์๋น์ค ์์
}
// ๋ฐ์ธ๋ : bindService()๋ฅผ ํธ์ถํ ๋ ์คํ๋๋ ํจ์, ๋ฐ์ธ๋๊ฐ ํ์์๋ค๋ฉด null์ ๋ฐํํ๋ฉด ๋จ
override fun onBind(intent: Intent?): IBinder? { // IBinder : ์๋น์ค์ ๊ตฌ์ฑ์์๋ฅผ ์ด์ด์ฃผ๋ ๋งค๊ฐ์ฒด ์ญํ
return mBinder // ์์์ ๋ง๋ค์ด๋ ๋ฐ์ธ๋ ๊ฐ์ฒด๋ฅผ ๋ฐํ๊ฐ์ผ๋ก ์ ๋ฌ
}
// ์์๋ ์ํ & ๋ฐฑ๊ทธ๋ผ์ด๋ : startForegroundService() ํธ์ถํ ๋ ์คํ๋๋ ํจ์
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY // START_STICKY, START_NOT_STICKY, START_REDELIVER_INTENT ์ค ํ๋ ๋ฐํ
// START_STICKY : ์์คํ
์ด ์๋น์ค๋ฅผ ์ค๋จํ๋ฉด ์๋น์ค๋ฅผ ๋ค์ ์คํํ๊ณ onStartCommand() ํธ์ถ
// START_NOT_STICKY : ์์คํ
์ด ์๋น์ค๋ฅผ ์ค๋จ์ํค๋ฉด ์๋น์ค๋ฅผ ์ฌ์์ฑํ์ง ์์
// START_REDELIVER_INTENT : ์์คํ
์ด ์๋น์ค๋ฅผ ์ค๋จํ๋ฉด ์๋น์ค๋ฅผ ๋ค์ ์คํํ๊ณ onStartCommnad() ํธ์ถ
// +์๋น์ค ์ข
๋ฃ ์ ๋ง์ง๋ง์ผ๋ก ์ ๋ฌ๋ ์ธํ
ํธ ์ฌ์ ๋ฌ. (๋ฐ๋์ ๋ช
๋ น์ ์คํํด์ผ ํ๋ ๊ฒฝ์ฐ)
}
// ์๋น์ค ์ข
๋ฃ : onCreate()์์ ์ํํ์์ค์ ๋ณด์ฌ์ฃผ์๋ ์๋ฆผ ํด์
override fun onDestroy() {
super.onDestroy()
stopForeground(true) // ์๋น์ค๊ฐ ์ค๋จ๋ ๋ ํฌ๊ทธ๋ผ์ด๋ ์๋น์ค ๋ฉ์ถค
}
fun startForegroundService() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
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(222, notification) // ์ธ์๋ก ์๋ฆผ ID ์๋ณ์์ ์๋ฆผ ์ง์ , ๊ทธ๋ฐ๋ฐ ์ด ๋ถ๋ถ์์ ์ค๋ฅ๋์ ๊บผ์ง, ์ฃผ์ ์ฒ๋ฆฌ ํ ๊ฒ
}
fun isPlaying(): Boolean {
return (mMediaPlayer != null && mMediaPlayer?.isPlaying ?: false)
}
fun play() {
if (mMediaPlayer == null) {
// ์์
ํ์ผ์ ๋ฆฌ์์ค๋ฅผ ๊ฐ์ ธ์ ๋ฏธ๋์ด ํ๋ ์ด์ด ๊ฐ์ฒด ํ ๋น
mMediaPlayer = MediaPlayer.create(this, R.raw.singing_mp3)
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
}
}
}
}
๐ก ๊ฐ๋จํ ์กํฐ๋นํฐ ์ฝ๋ ํ
package com.limheejin.musicplayer
import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.limheejin.musicplayer.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() , View.OnClickListener { // ๋ทฐ๋ฅผ ํด๋ฆญํ์ ๋ ํ๋ ์ ํด์ฃผ๋๋ก ๊ตฌํ
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
enableEdgeToEdge()
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
binding.btnPlay.setOnClickListener(this)
binding.btnPause.setOnClickListener(this)
binding.btnStop.setOnClickListener(this)
}
override fun onClick(v: View?) {
when(v?.id){
R.id.btn_play -> {
play()
}
R.id.btn_pause -> {
pause()
}
R.id.btn_stop -> {
stop()
}
}
}
override fun onResume() {
super.onResume()
}
override fun onPause() {
super.onPause()
}
private fun play() {
}
private fun pause() {
}
private fun stop() {
}
}
๐ก ์์ธํ ์กํฐ๋นํฐ ์ฝ๋
package com.limheejin.musicplayer
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Build
import android.os.Build.VERSION_CODES.P
import android.os.Bundle
import android.os.IBinder
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.limheejin.musicplayer.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity(), View.OnClickListener { // ๋ทฐ๋ฅผ ํด๋ฆญํ์ ๋ ํ๋ ์ ํด์ฃผ๋๋ก ๊ตฌํ
private lateinit var binding: ActivityMainBinding
private var mService: MusicPlayerService? = null // ์๋น์ค ๋ณ์
private val mServiceConnection = object : ServiceConnection { // ์๋น์ค์ ๊ตฌ์ฑ์์ ์ฐ๊ฒฐ ์ํ ๋ชจ๋ํฐ๋ง
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mService =
(service as MusicPlayerService.MusicPlayerBinder).getService() // MusicPlayerBinder๋ก ํ๋ณํ
}
override fun onServiceDisconnected(name: ComponentName?) {
mService = null // ๋ง์ฝ ์๋น์ค๊ฐ ๋๊ธฐ๋ฉด, mService๋ฅผ null๋ก ๋ง๋ฆ
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
enableEdgeToEdge()
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
binding.btnPlay.setOnClickListener(this)
binding.btnPause.setOnClickListener(this)
binding.btnStop.setOnClickListener(this)
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.btn_play -> {
play()
}
R.id.btn_pause -> {
pause()
}
R.id.btn_stop -> {
stop()
}
}
}
override fun onResume() {
super.onResume()
// ์๋น์ค ์คํ
if (mService == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
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) // ๋ฐ์ธ๋ํ ์์ ์ ์๋น์ค๊ฐ ์คํ๋์ง ์์ผ๋ฉด AUTO CREATE
}
// onPause() : ์ฌ์ฉ์๊ฐ ์กํฐ๋นํฐ๋ฅผ ๋ ๋ฌ์ ๋ ์คํ
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()
}
}
๐ฅ ์์ฑ
- ์๋น์ค๋ฅผ ์ด์ฉํ ์์ ์ฌ์ ์ฑ์ด ์์ฑ๋์๋ค.
- ๋ค๋ง, ๋ช ๊ฐ์ง ๋ฌธ์ ์ ์ด ์๋๋ฐ
(1) ์๋น์ค์ startForeground ์์ ์ค๋ฅ๊ฐ ๋์ ์ฑ์ด ๋ค์ด๋๋ ๋ฌธ์
(2) ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์์ ์ฌ์์ด ์งํ์ด ๋๋, ์ ์ ์ผ๋ก ์ธํด ๋ฉ๋ชจ๋ฆฌ์์ ๊ธ๋ฐฉ ์ฑ์ด ํฌ๋ง๋์ด ๋ ธ๋๊ฐ ๋ฉ์ถ๋ ๋ฌธ์
์์ ๊ฐ์ ๋ฌธ์ ์ ๋ค์ด ์์๋ค.
๋ฐ์ํ
๐ฌ C O M M E N T