๐ SeSAC์ 'JetPack๊ณผ Kotlin์ ํ์ฉํ Android App ๊ฐ๋ฐ' ๊ฐ์ข๋ฅผ ์ ๋ฆฌํ ๊ธ ์ ๋๋ค.
์๋๋ก์ด๋ 4๊ฐ ์ปดํฌ๋ํธ ์ค ํ๋๋ก ๋ฐฑ๊ทธ๋ผ์ด๋ ์
๋ฌด
๋ฅผ ๋ด๋นํ๊ธฐ ์ํ ์ปดํฌ๋ํธ.
๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ค๋ ์๊ฐ ๋์ ์ํ๋๋ ์ ๋ฌด๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ์ปดํฌ๋ํธ
ํ๋ฉด ์ถ๋ ฅ ๋ฅ๋ ฅ์ ๊ฐ์ง์ง ์๋ ์ปดํฌ๋ํธ
Thread ํน์ Coroutine ๋ฅผ ์ฌ์ฉํ์ฌ ์กํฐ๋นํฐ์์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ ๋ฌด๋ฅผ ์์ ํ ์ ์๋ค. ๊ทธ๋ผ only ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ ์ธ ์๋น์ค์ ๋ฌด์์ด ๋ค๋ฅผ๊น?
ํ๋ฉด์์ ๋ฐ์์ ๋ฐ๊ฑฐ๋ ์ฒ๋ฆฌ๋ ๋ฐ์ดํฐ๋ฅผ ํ๋ฉด์ ์ถ๋ ฅํด์ผ ๋๋ ๊ฒฝ์ฐ ๊ฐ์ ํ๋ฉด๊ณผ ๊ด๋ จ๋ ์ ๋ฌด๋ผ๋ฉด ์กํฐ๋นํฐ์์ ์ฒ๋ฆฌํ๋ ๊ฒ์ด ๋ง๋ค. ๋๋ฌธ์ ๋ชจ๋ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ ๋ฌด๋ฅผ ์๋น์ค๋ก ๋นผ๋ ๊ฒ์ ์ข์ง ์๋ค. ๋ฌผ๋ก ํ๋ฉด ๊ด๋ จ ์์ ๋ ๊ฐ์ ์ ์ผ๋ก ์๋น์ค๋ก ๋บ ์ ์๊ธด ํ๋ค๊ณ ํ๋ค.
๊ทธ๋์ ์๋น์ค๋ ํ๋ฉด ๋ฐ์์ฑ์ด ์์ ์๊ฑฐ๋ ๋๋ฌผ๊ฒ ๋ฐ์ํ๋ ์ ๋ฌด๋ฅผ ๋ด๋นํ๋ค. ํนํ ์ ์ ํธํธํฐ ํ๋ฉด์ ๋ค๋ฅธ ์ฑ์ด ๋์์ ธ ์์ด๋ ์ฐ๋ฆฌ ์ฑ์ด ์์ ์ ์ฒ๋ฆฌํ๋ ์ํฉ์ ๋ํ์ ์ผ๋ก ๋ค ์ ์๋ค.
class MyService : Service() {
override fun onBind(intent: Intent): IBinder? {
return null
}
}
Service ๋ฅผ ์์ ๋ฐ์ ์์ฑ
onBind()
ํจ์๋ ๋ด๋ถ๋ฅผ ๊ตฌํํ์ง ์๋๋ผ๋ ๋ฐ๋์ ์ค๋ฒ๋ผ์ด๋ ๋ฐ์์ผ ํ๋ค.
์๋น์ค๋ ์ปดํฌ๋ํธ์ด๊ธฐ ๋๋ฌธ์ ๋ฑ๋กํด ์ฃผ์ด์ผ ํ๋ค.
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
AndroidManifest.xml ์ ๋ฑ๋ก
<service>
ํ๊ทธ๋ก ๋ฑ๋กํ๋ฉฐ <name>
์์ฑ์ ์๋ต ๋ถ๊ฐ
val intent = Intent(this, MyService::class.java)
startService(intent)
startService()
์ ์ํ ์คํ
์ธ๋ถ ์ฑ์ ์๋น์ค๋ผ๋ฉด setPackage() ํจ์๋ฅผ ์ด์ฉํด ์คํํ๊ณ ์ ํ๋ ์ฑ์ ํจํค์ง๋ช
(์๋ณ์) ์ ๋ช
์
4๊ฐ์ ์ปดํฌ๋ํธ ์ค์ ์ ์ผํ๊ฒ ์๋น์ค๋ง ์ข ๋ฃํ๊ธฐ ์ํ ์ธํ ํธ ํจ์๋ ์กด์ฌํ๋ค.
์๋น์ค๋ ํ ๋ฒ ๊ตฌ๋๋๋ฉด ๋๋ถ๋ถ ์์ ์๊ฐ์ด ๊ธธ๊ณ , ํ๋ฉด ๋ฐ์์ฑ์ด ์์ด ์ ์ ์ ์ํด ์ข ๋ฃ๋ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
val intent = Intent(this, MyService::class.java)
stopService(intent)
์๋๋ก์ด๋ ์ปดํฌ๋ํธ ์ค์์ ์กํฐ๋นํฐ์ ์๋น์ค์ ๋ผ์ดํ์ฌ์ดํด์ ์ ๋ฆฌํด ๋์์ผ ํ๋ค.
์๋น์ค๋ฅผ ์คํ์ํค๋ ๋ฐฉ๋ฒ์ด startService()
ํจ์์ bindService()
ํจ์ ๋ ๊ฐ๊ฐ ์๋ค.
์ด๋ ํจ์๋ฅผ ์ด์ฉํด ์๋น์ค๊ฐ ์คํ ๋๋์ง์ ๋ฐ๋ผ ์๋น์ค์ ๋ผ์ดํ ์ฌ์ดํด์ด ๋ฌ๋ผ์ง๋ค.
์๋น์ค ํด๋์ค๋ ์ฑ๊ธํค์ผ๋ก ๋์ํ๋ค.
์ต์ด์ ์๋น์ค ์ธํ
ํธ ๋ฐ์ ์ onCreate()
๊ฐ ํธ์ถ ๋๋ฉด์ ์๋น์ค ๊ฐ์ฒด๊ฐ ์์ฑ๋๋ค. ๊ทธ๋ฆฌ๊ณ stopService()
ํธ์ถ ์ onDestroy()
๊ฐ ํธ์ถ๋๋ฉด์ ์๋น์ค๋ ๋ฉ์ถ๋ค. ๊ทธ๋ฐ๋ฐ ๋ง์ฝ ์๋น์ค ๊ตฌ๋ ์ค์
๋ ๋ฒ์งธ ์ธํ
ํธ๊ฐ ๋ฐ์ํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
onStartCommand()
๋ง ๋ค์ ํธ์ถ๋๋ค. ์๋น์ค๋ ์ฑ๊ธํค์ด๊ธฐ ๋๋ฌธ์ onCreate()
ํจ์๋ ์๋น์ค ๊ฐ์ฒด ์์ฑ ์ ์ต์ด์ ๋จ ํ๋ฒ๋ง ํธ์ถ๋๋ค. ๊ทธ๋์ ์๋น์ค ์์ฒญ์ ๊ตฌ๋ถํด์ ์ฒ๋ฆฌํ๊ณ ์ถ๋ค๋ฉด onStartCommand()
์์ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌํํด์ผ ํ๋ค. ๋ด๋ถ์ ๋ฉํฐ ์ค๋ ๋ ํ๊ฒฝ์ ๊ตฌํํ๋ ์์ผ๋ก ๋ง์ด๋ค.
์๋น์ค ์์ ์ ์คํ์ํจ ์ธํ
ํธ ์ ๋ณด๋ onStartCommand()
์ ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌ๋๋ค.
startService ์์ ์ฐจ์ด์ ์ onCreate() ํธ์ถ ์ดํ onBind()
์ onUnbind()
๊ฐ ํธ์ถ ๋๋ค๋ ๊ฒ์ด๋ค. ์ฑ๊ธํค์ด๊ธฐ ๋๋ฌธ์ ๋์ผํ๊ฒ ์ธํ
ํธ๊ฐ ๋ฐ์ํ๋ฉด onBind()
๋ง ๋ค์ ํธ์ถ๋ ํ, ์ข
๋ฃ๋ ๋ ๋ค์ onUnbind()
ํจ์๊ฐ ํธ์ถ ๋๋ค.
startService() ํจ์์ ์ํด Service ๊ฐ ์คํ๋๋ฉด ํน์ ๊ฐ์ฒด๊ฐ ๋ฆฌํด๋์ง ์๋๋ค.
์ํธ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ ์ง์ ์ ์ธ ๋ฐฉ๋ฒ์ด ์๋ค.
BroadcastReceiver ๋ฅผ ์ ์ํ๊ณ ๋ฐ์ดํฐ ์ ๋ฌ์ด ํ์ํ ๋ ์ด BroadcastReceiver ๋ฅผ ์คํ ์ํค๋ฉด์ Broadcast Intent ์ Extra ๋ฐ์ดํฐ๋ก ์ ๋ฌ์ํค๋ ๋ฐฉ๋ฒ์ ์ด์ฉ
๊ตฌ๋๋๊ณ ์๋ ์๋น์ค์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ฑฐ๋ ๋ฐ์์ผ ํ๋ ์ํฉ์ด ์์ ์ ์๋ค. ์ด ๋ putExtra ๋ก ๋ฐ์ดํฐ ์ ๋ฌ์ด ๊ฐ๋ฅํ๋ค๊ณ ์๊ฐํ ์ ์์ง๋ง, ์ด๊ฒ์ ์ธํ ํธ๋ฅผ ๋ฐ์์ํจ ์๊ฐ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ด๋ค. ๋ฐ๋ผ์ ์์ ๋กญ๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๊ธฐ ์ํด์๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ด ํ์ํ๋ค.
์์ ๋ด์ฉ์ฒ๋ผ ์๋น์ค๋ ํน์ ๊ฐ์ฒด๋ฅผ ๋ฆฌํดํ์ง ์๋๋ค. ์์ ์ ์คํ์ํจ ๊ณณ์ ๋ฌด์ธ๊ฐ๋ฅผ ๋ฆฌํดํ ์ ์๋ค๋ ์๋ฏธ์ด๋ค. ๋ํ ์๋น์ค ์์ฒด์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ๋ถ๋ถ๋ ์๋ค. ๊ทธ๋์ BroadcastReceiver ๋ฅผ ์ด์ฉํ๋ค. ์๋น์ค ์์ ๋ฆฌ์๋ฒ๋ฅผ ๊ตฌํํ๋ ๊ฒ์ด๋ค.
์๋น์ค ๋ด๋ถ์ ๊ตฌํ๋ ๋ฆฌ์๋ฒ๊ฐ ์์คํธ๋ผ ๋ฐ์ดํฐ๋ก ๋์ด์จ ๊ฐ์ ์ถ์ถํ์ฌ ์๋น์ค์ ๋๊ฒจ์ฃผ๊ณ , ์๋น์ค๊ฐ ์ฌ์ฉํ ์ ์๊ฒ ๊ตฌํํด ์ฃผ๋ฉด ๋๋ค.
์๋น์ค์์ ์กํฐ๋นํฐ๋ก์ ๋ฐ์ดํฐ ์ ๋ฌ๋ ๋์ผํ๋ค. ์กํฐ๋นํฐ์ ๋ฆฌ์๋ฒ๋ฅผ ๊ตฌํํ๊ณ ์ธํ ํธ๋ฅผ ๋ฐ์์ํค๋ ์๊ฐ ์์คํธ๋ผ ๋ฐ์ดํฐ๋ก ๋ฐ์ด ๋ฃ๋๋ค.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#303897">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:src="@drawable/background" />
<ImageView
android:id="@+id/startButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginLeft="24dp"
android:layout_marginBottom="36dp"
android:clickable="true"
android:src="@drawable/ic_play" />
<ImageView
android:id="@+id/stopButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="24dp"
android:layout_marginBottom="36dp"
android:clickable="true"
android:src="@drawable/ic_stop" />
</RelativeLayout>
package com.kotdev99.android.c71
class MyService : Service() {
lateinit var player: MediaPlayer
var receiver = object : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
val mode = p1?.getStringExtra("mode")
if (mode != null) {
if (mode == "start") {
try {
if (player != null && player.isPlaying) {
player.stop()
player.release()
}
player = MediaPlayer.create(p0, R.raw.music)
player.start()
} catch (e: Exception) {
e.printStackTrace()
}
} else {
if (player != null && player.isPlaying) {
player.stop()
}
}
}
}
}
override fun onCreate() {
super.onCreate()
player = MediaPlayer()
registerReceiver(receiver, IntentFilter("PLAY_TO_SERVICE"))
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(receiver)
}
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
}
package com.kotdev99.android.c71
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val startButton = findViewById<ImageView>(R.id.startButton)
val stopButton = findViewById<ImageView>(R.id.stopButton)
startButton.setOnClickListener {
val intent = Intent("PLAY_TO_SERVICE")
intent.putExtra("mode", "start")
sendBroadcast(intent)
startButton.isEnabled = false
stopButton.isEnabled = true
}
stopButton.setOnClickListener {
val intent = Intent("PLAY_TO_SERVICE")
intent.putExtra("mode", "stop")
sendBroadcast(intent)
startButton.isEnabled = true
stopButton.isEnabled = false
}
val intent = Intent(this, MyService::class.java)
startService(intent)
}
override fun onDestroy() {
super.onDestroy()
val intent = Intent(this, MyService::class.java)
stopService(intent)
}
}
์๋น์ค ์์ ์ ์คํ์ํจ ๊ณณ์ '๊ฐ์ฒด๋ฅผ ๋๊ฒจ์ค๋ค (๋ฐ์ธ๋ฉ ํด์ค๋ค)' ๋ผ๋ ์๋ฏธ์์ bindService()
์๋น์ค๋ฅผ ๊ตฌ๋ํ ๊ณณ์ ์๋น์ค์์ ์ค๋นํ ๊ฐ์ฒด๋ฅผ ๋ฐ์ธ๋ฉํ๋ ๊ฒ์ผ๋ก, ์๋น์ค๋ฅผ ๊ตฌ๋ํ ์ชฝ์์ ๊ฐ์ฒด์ ์๋ ํจ์๋ฅผ ์ฝ ํ ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ํจ์๋ฅผ ํธ์ถํ๋ฉด์ ๋งค๊ฐ๋ณ์๋ ๋ฆฌํด ๊ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ ์ ์๋ค.
์ฆ, bindService
๋ฅผ ์ด์ฉํ์ฌ ๋ ์ปดํฌ๋ํธ ์ฌ์ด์์ ๋ฐ์ดํฐ ์ฃผ๊ณ ๋ฐ์ ์ ์๋ค.
class MyBinder: Binder() {
fun funA(arg: Int) {
}
fun funB(arg: Int): Int {
return arg * arg
}
}
override fun onBind(intent: Intent): IBinder? {
return MyBinder()
}
onBind()
ํจ์์ ๋ฆฌํด ๊ฐ์ฒด๊ฐ ์๋น์ค ํธ์ถ ์ชฝ์ ์ ๋ฌ๋๋ค. ๊ทธ๋์ ์๋น์ค ๋ด์ ๊ฐ์ฒด๋ฅผ ํ๋ ์ค๋นํด์ ๋ฆฌํด์์ผ ์ฃผ์ด์ผ ํ๋๋ฐ, IBinder ํ์
์ด์ด์ผ ํ๋ค. IBinder ๋ ์ธํฐํ์ด์ค์ด๊ณ ์ด๊ฒ์ ๊ตฌํํ ๊ฒ์ด Binder
์ด๊ธฐ ๋๋ฌธ์ Binder
๋ฅผ ์์ ๋ฐ์ ํด๋์ค๋ก ์ค๋นํด์ผ ํ๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ํด๋์ค์ ๊ฐ์ฒด๋ฅผ ๋ฆฌํด์์ผ ์ฃผ์ด์ผ ํ๋ค.
์ฐธ๊ณ ๋ก Binder ์์ ์ค๋ฒ๋ผ์ด๋ ๋ฐ์์ผ ํ๋ ํจ์๋ ์๋ค. ๊ทธ๋ฅ ์์๋ง Binder ๋ก ๋ฐ์ผ๋ฉด ๋๋ค.
val connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
servideBinder = service as MyService.MyBinder
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
ServiceConnection ์ ๊ตฌํํ ๊ฐ์ฒด ์ค๋น
onServiceConnected() ํจ์์ ๋งค๊ฐ๋ณ์๋ก Bind ๊ฐ์ฒด ์ ๋ฌ
์๋น์ค๋ฅผ ์ฌ์ฉํ๋ ์ชฝ์์ ์ค๋นํ๋ ๊ฒ์ด๋ค. ์ธํ
ํธ๋ฅผ ์ค๋นํ๋ ๊ฒ ์ธ์ ServiceConnection
์ด๋ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ๊ฐ์ฒด๋ฅผ ์ค๋นํด ์ฃผ์ด์ผ ํ๋ค. 2๊ฐ์ ํจ์๋ฅผ ์ค๋ฒ๋ผ์ด๋ ๋ฐ์์ผ ํ๋ค.
bindService ๋ก ์๋น์ค ์ชฝ๊ณผ ์ฐ๋์ด ๋์์ ๋ onServiceConnected()
๊ฐ ์๋ ํธ์ถ๋๋ค. ๊ทธ๋ฌ๋ฉด์ ๋ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก ์๋น์ค์์ ๋ฆฌํดํ ๊ฐ์ฒด๊ฐ ๋์ด์จ๋ค. ๊ทธ๋ฌ๋ค ์ด๋ ์๊ฐ ๋ฐ์ธ๋ฉ์ด ๋์ด์ง ์๊ฐ onServiceDisconnected()
ํจ์๊ฐ ์๋ ํธ์ถ๋๋ค.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#303897">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:src="@drawable/background" />
<ImageView
android:id="@+id/startButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginLeft="24dp"
android:layout_marginBottom="36dp"
android:clickable="true"
android:src="@drawable/ic_play" />
<ImageView
android:id="@+id/stopButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:layout_marginRight="24dp"
android:layout_marginBottom="36dp"
android:clickable="true"
android:src="@drawable/ic_stop" />
</RelativeLayout>
package com.kotdev99.android.c72
class MyService : Service() {
inner class MyBinder : Binder() {
var player = MediaPlayer()
fun startMusic() {
try {
if (player != null && player.isPlaying) {
player.stop()
player.release()
}
player = MediaPlayer.create(applicationContext, R.raw.music)
player.start()
} catch (e: Exception) {
e.printStackTrace()
}
}
fun stopMusic() {
if (player != null && player.isPlaying) {
player.stop()
}
}
}
override fun onBind(intent: Intent): IBinder {
return MyBinder()
}
}
package com.kotdev99.android.c72
class MainActivity : AppCompatActivity() {
lateinit var binder: MyService.MyBinder
val connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
binder = p1 as MyService.MyBinder
}
override fun onServiceDisconnected(p0: ComponentName?) {
TODO("Not yet implemented")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val startButton = findViewById<ImageView>(R.id.startButton)
val stopButton = findViewById<ImageView>(R.id.stopButton)
startButton.setOnClickListener {
binder.startMusic()
startButton.isEnabled = false
stopButton.isEnabled = true
}
stopButton.setOnClickListener {
binder.stopMusic()
startButton.isEnabled = true
stopButton.isEnabled = false
}
val intent = Intent(this, MyService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
override fun onDestroy() {
super.onDestroy()
unbindService(connection)
}
}
๊ฒฐ๊ณผ๋ ๋์ผํ๋ค.