바인딩된 서비스
앱 컴포넌트가 bindService()
를 호출하여 연결하는 서비스.
서비스에서 바인딩을 허용하려면 다음 두 가지가 필요하다.
IBinder
클라이언트와 서비스가 상호작용할 때 사용하는 인터페이스.ServiceConnection
클라이언트와 서비스의 연결 상태를 모니터링한다.
클라이언트는 여러 개가 동시에 한 서비스에 바인딩될 수 있다. 서비스는 바인딩되어 있는 클라이언트가 하나라도 있는 이상 중지되지 않고, 모든 클라이언트가 바인딩을 해제해야 중지되고 소멸한다.
Binder 클래스 상속받기
서비스를 클라이언트와 같은 앱, 프로세스에서만 사용한다면 Binder
클래스를 상속받아 사용할 수 있다. 이는 IBinder
인터페이스를 구현하는 클래스로, 클라이언트와 서비스 사이를 연결하는 인터페이스 역할을 한다.
Binder
를 사용하는 과정은 다음과 같다.
ServiceConnection
인터페이스를 구현한다. 다음 두 콜백 메서드를 구현하는 것이 중요하다.
onServiceConnected(name: ComponentName!, service: IBinder!): Unit
클라이언트와 서비스가 연결되었을 때 호출되는 콜백.
파라미터 타입 설명 name
ComponentName!
연결된 서비스 클래스의 이름. service
IBinder!
서비스의 onBind()
콜백에서 반환된 객체. 포함된 메서드 등을 사용하여 서비스와 상호작용할 수 있다.onServiceDisconnected(name: ComponentName!): Unit
클라이언트와 서비스의 연결이 예상치 못하게 해제되었을 때 호출되는 콜백. 일반적으로 서비스가 중지되거나 비정상 종료되었을 때 호출된다.
파라미터 타입 설명 name
ComponentName!
연결이 해제된 서비스 클래스의 이름.
- 클라이언트에서
bindService()
를 호출한다. 파라미터로 서비스를 시작하는Intent
와 위에서 구현한ServiceConnection
객체를 전달해야 한다.
bindService(service: Intent!, conn: ServiceConnection, flags: Int): Boolean
파라미터 타입 설명 service
Intent!
바인딩할 서비스를 실행하는 명시적 인텐트. conn
ServiceConnection
서비스와의 연결 상태를 모니터링하는 객체. flags
Int
바인딩 동작 옵션을 나타내는 플래그. 0
또는Context.BIND_*
상수를 조합한 값을 사용할 수 있다.반환값 타입 설명 Boolean
서비스가 존재하고 클라이언트가 바인딩될 때 요구되는 권한을 가지고 있으면 true
를 반환한다. 반대로 서비스가 존재하지 않거나 클라이언트가 바인딩될 때 요구되는 권한을 가지고 있지 않으면false
를 반환한다.
- 서비스에서 다음 중 하나를 만족하는
Binder
인스턴스를 생성한다.
- 클라이언트가 호출할 수 있는 공용(public) 메서드를 포함한다.
- 현재
Service
인스턴스(클라이언트가 호출할 수 있는 공용 메서드를 포함)를 반환한다. - 서비스(클라이언트가 호출할 수 있는 공용 메서드를 포함)가 호스팅하는 다른 클래스의 인스턴스를 반환한다.
- 서비스의
onBind()
콜백 메서드에서 이Binder
인스턴스가 반환된다.onBind(intent: Intent!): IBinder?
파라미터 타입 설명 intent
Intent!
클라이언트가 서비스에 바인딩하기 위해 bindService()
에 전달하는 인텐트.반환값 타입 설명 IBinder?
클라이언트와 서비스가 상호작용할 때 사용하는 인터페이스
- 반환된
Binder
가bindService()
에 전달했던ServiceConnection
객체의onServiceConnection()
콜백에 전달된다. 클라이언트는 이Binder
에 포함된 메서드를 사용하여 서비스와 상호작용할 수 있다
다음은 Binder
를 구현하여 클라이언트가 서비스의 메서드를 사용할 수 있게 하는 서비스 예시다.
class LocalService : Service() {
// 클라이언트에 제공되는 Binder
private val binder = LocalBinder()
// 난수 생성기
private val mGenerator = Random()
// 클라이언트가 사용할 서비스의 메서드
val randomNumber: Int
get() = mGenerator.nextInt(100)
// 클라이언트에 제공되는 Binder 클래스
// 이 서비스는 항상 클라이언트와 같은 프로세스에서 실행되기 때문에 IPC를 고려하지 않아도 된다.
inner class LocalBinder : Binder() {
// 현재 LocalService 인스턴스를 반환하여 클라이언트가 서비스의 공용 메서드를 사용할 수 있다.
fun getService(): LocalService = this@LocalService
}
override fun onBind(intent: Intent): IBinder {
return binder
}
}
LocalService.getService()
는 현재 LocalService
인스턴스를 반환한다. 이 반환된 인스턴스를 사용하여 클라이언트에서 LocalService
의 메서드를 호출할 수 있다. 위 예시에서는 LocalService.getRandomNumber()
를 호출할 수 있다.
아래는 버튼을 누를 때 LocalService.getRandomNumber()
를 호출하는 액티비티 예시다.
class BindingActivity : Activity() {
private lateinit var mService: LocalService
private var mBound: Boolean = false
// 서비스를 바인딩하는 데 사용하는 콜백을 정의한다. 이는 bindService()에 전달된다.
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// LocalService에 바인딩되었으므로 IBinder를 캐스팅하여 LocalService 인스턴스를 가져온다.
val binder = service as LocalService.LocalBinder
mService = binder.getService()
mBound = true
}
override fun onServiceDisconnected(arg0: ComponentName) {
mBound = false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
}
override fun onStart() {
super.onStart()
// LocalService에 바인딩
Intent(this, LocalService::class.java).also { intent ->
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
// LocalService에서 바인딩 해제
unbindService(connection)
mBound = false
}
// 버튼을 클릭할 때 호출됨 (레이아웃 파일에서 버튼의 android:onClick 속성 사용)
fun onButtonClick(v: View) {
if (mBound) {
// LocalService의 메서드를 호출한다.
// 이 메서드를 완료하는데 오래 걸린다면 ANR을 방지하기 위해 별도 스레드에서 호출해야 한다.
val num: Int = mService.randomNumber
Toast.makeText(this, "number: $num", Toast.LENGTH_SHORT).show()
}
}
}
Messenger 사용하기
서비스를 다른 프로세스에서 사용해야 할 경우 Messenger
를 통해 상호작용 인터페이스를 제공할 수 있다.
Messenger
를 사용하는 방법을 간략히 나타내면 다음과 같다.
- 서비스에서 클라이언트의 요청을 수신하는
Handler
를 구현한다. - 서비스에서
Handler
를 사용하여Messenger
를 생성한다.
Messenger(target: Handler!)
파라미터 타입 설명 target
Handler!
현재 Messenger
가 전송하는Message
를 수신하는Handler
.Messenger(target: IBinder!)
파라미터 타입 설명 target
IBinder!
현재 Messenger
가 상호작용해야 하는IBinder
.
Messenger
가 서비스의onBind()
에서 반환될IBinder
를 생성한다.- 클라이언트가
IBinder
를 사용하여Messenger
를 생성하고 이를 사용하여 서비스에Message
를 전송한다. - 클라이언트가 전송한
Message
를 서비스의Handler
가 수신한다.
이 방법은 바인더처럼 클라이언트가 서비스의 메서드를 호출할 수 있는 것이 아니라, 서비스에게 메시지(Message
객체)를 전달하는 방식이다.
아래는 Messenger
를 사용하는 간단한 서비스 예시다.
// 서비스가 메시지를 표시하도록 하는 메시지 코드
private const val MSG_SAY_HELLO = 1
class MessengerService : Service() {
// 클라이언트가 IncomingHandler에게 메시지를 전송하는데 사용하는 메신저
private lateinit var mMessenger: Messenger
// 클라이언트가 전송하는 메시지를 처리하는 Handler
internal class IncomingHandler(
context: Context,
private val applicationContext: Context = context.applicationContext
) : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
MSG_SAY_HELLO -> Toast.makeText(
applicationContext, "hello!", Toast.LENGTH_SHORT).show()
else -> super.handleMessage(msg)
}
}
}
// 클라이언트가 서비스에 바인딩될 때 메신저에 인터페이스를 반환하면 이를 사용하여 메시지를 전송한다.
override fun onBind(intent: Intent): IBinder? {
Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()
mMessenger = Messenger(IncomingHandler(this))
return mMessenger.binder
}
}
Handler
는 handleMessage()
에서 Message
를 수신하고 이의 what
멤버로 메시지의 내용을 식별한다.
클라이언트가 할 일은 전달받은 IBinder
를 사용하여 Messenger
를 생성하고 send()
를 호출하여 메시지를 보내는 것뿐이다. 다음은 서비스에 바인딩되어 메시지를 보내는 액티비티 예시다.
class ActivityMessenger : Activity() {
// 서비스와 통신하는 Messenger
private var mService: Messenger? = null
// 액티비티가 서비스에 바인딩되었는지 여부
private var bound: Boolean = false
// 서비스의 메인 인터페이스와 상호작용하는 클래스
private val mConnection = object : ServiceConnection {
// 클라이언트와 서비스가 연결되었을 때 호출된다.
// 서비스의 onBind()에서 반환하는 IBinder가 전달된다.
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// IBinder를 이용하여 서비스의 Messenger를 클라이언트로 불러온다.
mService = Messenger(service)
bound = true
}
// 클라이언트와 서비스가 완전히 연결 해제되었을 때 호출된다.
override fun onServiceDisconnected(className: ComponentName) {
mService = null
bound = false
}
}
fun sayHello(v: View) {
if (!bound) return
// 새 Message를 생성하여 서비스에 보낸다.
val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
try {
mService?.send(msg)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main)
}
override fun onStart() {
super.onStart()
// 서비스에 바인딩
Intent(this, MessengerService::class.java).also { intent ->
bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
}
}
override fun onStop() {
super.onStop()
// 바인딩되어 있다면 해제
if (bound) {
unbindService(mConnection)
bound = false
}
}
}
바인딩된 서비스의 수명 주기
바인딩된 서비스를 사용할 때(바인딩만 허용한다고 가정) 호출되는 수명 주기 콜백 메서드는 다음과 같다.
onCreate(): Unit
서비스가 생성될 때 호출된다. 초기 설정 작업을 여기서 수행할 수 있다.onBind(intent: Intent!): IBinder?
클라이언트가 바인딩될 때 호출된다. 클라이언트가 서비스와 상호작용할 때 사용하는 바인더를 반환한다.onUnbind(intent: Intent!): Boolean
클라이언트와 바인딩이 해제될 때 호출된다. 반환값은onRebind()
의 호출 여부를 의미한다.onRebind(intent: Intent!): Unit
onUnbind()
가 호출된 후 새 클라이언트가 바인딩될 때 호출된다.onUnbind()
가true
를 반환할 때만 호출된다. 이 메서드는 반환값이 없지만 클라이언트의onServiceConnected()
콜백으로IBinder
가 전달된다.onDestroy(): Unit
서비스를 더 이상 사용하지 않아 소멸할 때 호출된다. 리소스 정리 작업을 여기서 수행할 수 있다.
바인딩된 서비스의 수명 주기는 다음 이미지에 설명된 것과 같이 동작한다.
'Android' 카테고리의 다른 글
JobScheduler로 백그라운드 작업 예약하기 (0) | 2022.09.05 |
---|---|
서비스 (3) : 시작되는 서비스 (0) | 2022.08.29 |
서비스 (1) : 포그라운드 및 백그라운드 서비스 (0) | 2022.08.27 |
액션 바 사용하기 (0) | 2022.08.09 |
알림 표시하기 (0) | 2022.07.26 |