Service는 App의 Background Thread로 동작한다고 소개했다.
activity에서 어떤 버튼을 눌러 Service에게 Intent를 매개로 데이터를 넘겨주는 것(Activity -> Service)은 쉬웠다.
그럼 반대로 Service ->Activity를 하기 위해선?
Service 용도
지난 시간에 배웠지만 복습해보자
UI와 관계없는 어떤 작업이 앱이 실행중이든 실행중이지 않든 (Background) 어떤 작업이 수행되도록 하고싶을 때
ex. music play service
즉 Service <->Activity 양방향 Communcation 을 할 수 있는 2가지 방법을 소개한다.
1. BroadCast System
BroadCastReceiver 를 모른다면 아래 글을 참고하시라
그림으로 표현하면 이런 느낌이다.
전체 코드를 업로드하기에는 양이 많고
MainActivity <-> Service 간 통신의 핵심이되는 부문만 살펴보자.
<MainActivity.kt>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
playListInitialize()
listViewInitialize()
// service -> Activity broadCastReceiver 등록
registerReceiver(receiver, IntentFilter(getString(R.string.service_to_main_intent_filter)))
val serviceIntent = Intent(this, MusicPlayService::class.java)
// MusicPlayService가 Background에서 동작
startService(serviceIntent)
}
// MainActivity 입장에서 Service에게 음악정보, 재생 상태를 '받고'싶은 상태
var receiver : BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// Service send state info to main receiver
val mode = intent?.getStringExtra("mode")
when (mode) {
"start" -> {
progressThreadIsAlive = true
// 음악 플레이 시간을 progress bar의 최대값으로
progress_bar.max = intent.getIntExtra("playtime", -1)
progress_bar.progress = progress_bar.min
}
"stop" -> {
progressThreadIsAlive = false
progress_bar.progress = progress_bar.min
}
}
}
}
registerReciever 메소드를 통해 BroadCastReiver를 등록한다.
이때 2nd parameter로 com.example.SERVICE_TO_ACTIVITY 를 넘긴다.
(Service->Activity) 로 오는 IntentFilter를 등록한다는 뜻.
<Service.kt>
var receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val mode = intent?.getStringExtra("mode")
when (mode) {
"start" -> {
song = intent.getStringExtra("songName")
// raw Resource내에서 songName을 그대로 찾아옴. (확장자 제외)
val songId = resources.getIdentifier(song, "raw", packageName)
// 기존것을 멈추고 새 노래 Play가능하게 해야함.
playerInitialize()
// 기존곡(플레이중이던 곡)이 아닐 경우 player 초기화하고 재생성
// songId는 resource getIdentifier기 때문에 파일 제목과 같아야함.
// 파일명 - 'song'변수 (확장자 mp3제외) 가 같아야함.
player = MediaPlayer.create(context, songId)
player!!.start()
val serviceToMainIntent = Intent(getString(R.string.service_to_main_intent_filter))
serviceToMainIntent.putExtra("mode", "start")
serviceToMainIntent.putExtra("playtime", player!!.duration)
sendBroadcast(serviceToMainIntent)
// 플레이어 종료시 수행됨
player!!.setOnCompletionListener {
val serviceToMainIntent = Intent(getString(R.string.service_to_main_intent_filter))
serviceToMainIntent.putExtra("mode", "stop")
sendBroadcast(serviceToMainIntent)
}
}
"stop" -> {
// null이거나 플레이중이면
playerInitialize()
}
}
}
}
override fun onCreate() {
super.onCreate()
registerReceiver(receiver, IntentFilter(getString(R.string.main_to_service_intent_filter)))
}
MainActivity와 마찬가지로
BroadCastReceiver를 생성ㅎ고
registerReceiver 메소드로 (com.example.ACTIVITY_TO_SERVICE) IntentFilter를 등록하여 열어둔다
이로써 MainActivity <-> Service간 BroadCastReceiver를 매개로 통신이 가능해진다.
2. bound Service and bind a component to Activity (communication Channel)
[BindService]
개요: Client-Server 에서 Server 역할
기능: Application component bind to service, request & reply, IPC
생명주기: App Component binding state // Background에서는 무한히 수행되지는 않는다.
필수 메소드 onBind()
- Client - Service Interaction 을 위해 binding 시점에 호출되는 메소드
- return : Ibinder object
- client는 bindService(), unbindService() 로 바인딩&해제
보통 Activity에서 Service Class를 대상으로 bind - unbind
[MusicPlayerService.kt]
class MusicPlayService : Service() {
lateinit var song : String
var player : MediaPlayer? = null
val binder = MyBinder()
// Return the communication channel to the service.
// May return null if clients can not bind to the service
override fun onBind(intent: Intent): IBinder {
return binder
}
inner class MyBinder : Binder() {
// Binder 객체는 서비스에 연결시 바인더 객체 정보를 Activity로 전달해줘야함.
// Activity가 외부에서 호출할 수 있는 함수를 미리 만들어놔야함.
// -> 외부에서 이 객체(MusicPlayService)를 얻는 메소드 getService()
fun getService() : MusicPlayService {
return this@MusicPlayService
}
}
}
[MainActivity.kt]
//BroadCast Receiver 대신 BindService 이용해보자
// MainActivity는 Client, MusicPlayService(BindService)는 Server 역할
class MainActivity : AppCompatActivity() {
lateinit var songList : Array<String>
lateinit var songName: String
var progressThreadIsAlive = false
var progressThread : Thread? = null
lateinit var musicPlayService : MusicPlayService
// binding state flag
var isBinding = false
// connectionResult는
//bindService() 메소드 호출시 2번째 파라미터에
// Disconnected or Connected 2가지 결과값을 반환받는다.
var connectionResult = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
isBinding = false
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
isBinding = true
// inner class를 매개로!
val binder = service as MusicPlayService.MyBinder
musicPlayService = binder.getService()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
playListInitialize()
listViewInitialize()
serviceBinding()
}
fun serviceBinding() {
val intent = Intent(this, MusicPlayService::class.java)
// 1st : intent, 2nd : Service Connection (Callback -> result varaible),
// 3rd : Flag Option (auto create -> if connection result is "DISCONNECTED" Auto Retry Connection!"
bindService(intent, connectionResult, Context.BIND_AUTO_CREATE)
}
override fun onDestroy() {
super.onDestroy()
if (isBinding) {
unbindService(connectionResult)
isBinding = false
}
}
}
MainActivity에서
serviceBindind( intent, connectionResult, OptionFlag) 호출
-> connectionResult == Connected
Connect성공시 binder (MyBinder Type) 를 service(IBinder Type)으로 리턴받는다.
이 service변수를MyBinder 타입으로 넘겨받는다.
Q. service는 대체 어디로부터 넘겨받는 것인지?
A. MusicPlayerService.kt 멤버 onBind 메소드의 "return bind" 에서 넘겨받는다.
service변수 (binder) 가 곧 'Communication Channel' 역할을 하는 것이다.
-> val binder = service as MusicPlayService.MyBinder
-> musicPlayService = binder.getService() // 별도의 MusicPlayerService 객체를 얻어오는데 성공.
☞ BroadCastReceiver 를 이용한 방법은 Activity, Service 양측에 모두 receiver를 등록하고
양방향이다 보니 Intent를 stop,play 메소드마다 모두 생성했어야했는데
bindService를 이용하는 방법은 binder라는 'Channel' 을 이용해서 한번만 binding해두면
두고두고 쓸 수 있어 코드가 더 간결하다.
1번 , 2번 방법 모두 커뮤니케이션 구현 방식이 다를뿐, 실행 결과는 동일하다.
[Reference]
https://www.brightdevelopers.com/android-tips-activity-service/
'Android' 카테고리의 다른 글
[Android] instance (0) | 2020.07.03 |
---|---|
[Android] Service 2편 (Foreground) (0) | 2020.06.22 |
[Android] Service (feat. Thread) (0) | 2020.06.20 |
[Android] drawable resize, customizing (0) | 2020.06.20 |
[Android] BroadCast Receiver (0) | 2020.06.18 |