연결된 서비스는 클라이언트-서버 인터페이스의 서버에 해당함
액티비티와 같은 컴포넌트가 서비스에 연결, 요청 전송, 응답 수신, IPC 수행을 가능하게 함
일반적으로 백그라운드에서 무기한으로 실행되지 않으며, 다른 앱 컴포넌트에게 서비스를 제공하는 동안만 작동함
연결된 서비스는 Service
클래스를 구현해 다른 앱이 연결되고 상호작용할 수 있게 함
서비스 연결을 제공하려면 onBind()
콜백 메소드를 구현해야 함, 이 메소드는 서비스와 상호작용하기 위해 클라이언트가 사용할 수 있는 프로그래밍 인터페이스를 정의한 IBinder
객체를 반환함
서비스 개요에서 설명했듯, 시작되고 동시에 연결되는 서비스를 만들 수 있음
startService()
로 서비스를 시작해 무기한으로 실행되게 하고 동시에 bindService()
로 클라이언트가 서비스와 연결하게 할 수 있음
서비스가 시작되고 연결될 수 있게 만들었다면 시스템은 서비스가 시작되었을 때 모든 클라이언트와 연결 해제되기 전에는 서비스를 파괴하지 않음, 대신 반드시 서비스를 stopSelf()
나 stopService()
를 호출해 명시적으로 중단시켜야 함
보통은 onBind()
나 onStartCommand()
중 하나만 구현하면 되지만, 둘 다 구현해야할 상황이 가끔 있음, 예를 들어 음악 플레이어 앱의 경우 액티비티가 사용자가 앱을 떠나도 음악을 재생할 수 있게 음악 재생 서비스를 시작하고, 사용자가 앱을 떠났다가 다시 돌아왔을 때 액티비티가 음악 재생의 제어권을 되찾을 수 있게 서비스가 연결을 제공하는 경우에 해당함
클라이언트가 bindService()
를 호출할 때 서비스와 연결을 모니터링하는 ServiceConnection
의 구현을 반환해야 함, 이 반환값은 요청하는 서비스가 존재하는지, 클라이언트가 해당 서비스에 액세스할 수 있는지 여부를 나타냄
안드로이드 시스템이 클라이언트와 서비스의 연결을 생성할 때 ServiceConnection
의 onServiceConnected()
를 호출함, 이 메소드는 클라이언트가 연결된 서비스와 통신할 때 사용할 수 있는 IBinder
가 인수로 포함됨
동시에 여러 클라이언트가 서비스에 연결될 수 있음, 이 때 시스템은 첫 클라이언트가 연결될 때만 onBind()
메소드를 호출해 IBinder
를 생성함, 다른 클라이언트가 연결될 때는 onBind()
를 다시 호출하지 않고 첫 클라이언트가 연결될 때 생성된 IBinder
를 전달함, 마지막 클라이언트가 연결 해제할 때 이 서비스가 시작된 서비스가 아니라면 시스템은 서비스를 파괴함
IBinder
를 제공해야 함, 인터페이스를 정의하는 세가지 방식이 있음Binder
클래스 확장서비스가 본인의 앱에서만 사용 가능하고 클라이언트와 같은 프로세스에서 실행된다면 Binder
클래스를 확장해 생성하고 onBind()
에서 객체를 반환해 인터페이스를 만듬
클라이언트가 Binder
를 수신하면 서비스나 수신된 Binder
객체의 공개(Public
) 메소드를 직접 사용할 수 있음
서비스가 본인 앱의 백그라운드 작업만 하는 경우 선호되는 방법
Messenger
사용작업을 다른 프로세스 사이에서 해야 한다면 Messenger
를 사용해 서비스를 위한 인터페이스를 생성할 수 있음, 이 경우 서비스는 다양한 타입의 Message
객체에 응답하기 위해 Handler
를 정의해야 함
Handler
는 클라이언트와 IBinder
를 공유할 수 있는 Messenger
의 기반이며, 클라이언트가 Message
객체를 사용해 서비스에 명령을 보낼 수 있도록 함
Messenger
는 모든 요청을 싱글 스레드에서 처리하기 때문에 IPC
를 수행하는 가장 간단한 방법
AIDL
사용AIDL(Android Interface Definition Language)
는 프로세스간 IPC
를 수행하기 위해 객체를 운영체제 시스템이 이해할 수 있도록 기본형으로 분해하고 marshall
함
참고: 마샬링(컴퓨터 과학)
Messenger
를 사용하는 이전 방법은 사실 AIDL
을 기본 구조로 사용함
서비스가 여러 요청을 동시에 대응하게 만들 때 AIDL을 직접 사용할 수 있음, 이 때 서비스는 스레드로부터 안전하게 설계되어야 하고 멀티스레딩을 지원해야 함
AIDL
을 직접 사용하려면 인터페이스를 정의하는 .aidl
파일을 생성해야 함, 안드로이드 SDK 도구는 인터페이스를 구현하고 IPC에 대응할 추상 클래스를 만드는데 이 파일을 사용함
노트: 대부분의 앱에서
AIDL
은 멀티스레딩을 지원해야할 수 있고, 그 결과로 더 복잡한 구현이 되기 때문에 연결된 서비스를 만드는 최고의 방법이 아닙니다. 그러므로 이 문서는 어떻게 사용하는지 언급하지 않습니다.AIDL
을 직접 사용하려면AIDL
문서를 확인하세요.
Binder
클래스 확장Binder
클래스의 구현할 수 있음노트: 이 방식은 클라이언트와 서비스가 같은 앱이고 같은 프로세스에 있을 경우에만 사용 가능합니다. 이는 매우 일반적인 경우입니다. 예를 들어, 음악을 백그라운드에서 재생하기 위해 서비스를 액티비티에 연결하는 음악 앱의 경우 잘 작동합니다.
설정하는 방법
Binder
객체를 서비스에서 생성함onBind()
콜백 메소드에서 이 Binder
객체를 반환함
클라이언트는 onServiceConnected()
콜백 메소드에서 Binder
를 수신해 사용 가능한 방법으로 연결된 서비스를 호출함
노트: 클라이언트가 제대로 반환된 객체를 캐스팅하고 그 객체의 API를 사용하려면 서비스와 클라이언트는 반드시 같은 앱에 있어야 합니다. 또한 이 방식은 프로세스간
marshalling
을 수행하지 않기 때문에 서비스와 클라이언트가 반드시 같은 프로세스 안에 있어야 합니다.
Binder
구현체를 통해 클라이언트가 서비스의 메소드에 액세스할 수 있도록 하는 서비스 예시
class LocalService : Service() {
// 클라이언트에게 제공되는 Binder 객체.
private val binder = LocalBinder()
// 난수 생성기.
private val mGenerator = Random()
/** 클라이언트가 사용할 메소드. */
val randomNumber: Int
get() = mGenerator.nextInt(100)
/**
* 클라이언트 바인더에 사용되는 클래스. 이 서비스는 항상 클라이언트와
* 같은 프로세스에서 실행되므로 IPC를 처리할 필요가 없습니다.
*/
inner class LocalBinder : Binder() {
// 이 LocalService 인스턴스를 반환해 클라이언트가 public 메서드를 호출할 수 있게 합니다.
fun getService(): LocalService = this@LocalService
}
override fun onBind(intent: Intent): IBinder {
return binder
}
}
LocalBinder
는 클라이언트에게 LocalService
의 현재 객체를 전송하기 위해 getService()
메소드를 제공함, 이는 클라이언트가 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()
unbindService(connection)
mBound = false
}
// 레이아웃 파일의 버튼이 android:onClick 속성으로 이 메서드에 연결되어 있을 때 클릭 시 호출됩니다.
fun onButtonClick(v: View) {
if (mBound) {
// LocalService의 메서드를 호출합니다.
// 그러나 이 호출이 오래 걸릴 수 있다면
// 액티비티 성능 저하를 방지하기 위해 별도의 스레드에서 요청해야 합니다.
val num: Int = mService.randomNumber
Toast.makeText(this, "number: $num", Toast.LENGTH_SHORT).show()
}
}
}
ServiceConnection
의 구현제와 onServiceConnected()
콜백을 사용해 클라이언트가 서비스에 연결되는 법을 보여줌, 노트: 위 예제는
onStop()
메소드에서 클라이언트와 서비스의 연결을 해제합니다. 클라이언트와 서비스가 언제 연결 해제하는 것이 적절한지는 뒤의 추가 참고사항 섹션에서 언급합니다.
Messenger
사용서비스가 원격 프로세스와 통신해야 한다면, Messenger
를 사용해 인터페이스를 제공할 수 있음
이 방식은 AIDL
을 사용하지 않고 IPC
를 수행할 수 있는 방식임
Messenger
는 서비스에 대한 모든 호출을 큐에서 처리하기 때문에 멀티스레딩을 반드시 대응해야하는 AIDL
인터페이스 방식보다 간단함
대부분의 앱에서 서비스는 멀티스레딩을 처리할 필요가 없기 때문에 Messenger
는 서비스가 호출을 한번에 하나씩 처리할 수 있게 만들어짐
Messenger
사용 요약
서비스에서 클라이언트의 호출에 대한 콜백을 받는 Handler
를 구현
서비스는 Messenger
객체(Handler
의 참조)를 생성하기 위해 Handler
를 사용함
서비스가 onBind()
클라이언트에게 반환하는 IBinder
를 Messenger
가 생성함
클라이언트가 Message
객체를 서비스에 전달하기 위해 IBinder
를 사용해 Messenger
(서비스의 Handler
를 참조)를 인스턴스화함
handleMessage()
메소드를 통해 서비스는 Handler
에서 각 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()
를 사용해 메시지를 보내면 됨
서비스에 연결하고 MSG_SAY_HELLO
메시지를 보내는 액티비티 예시
class ActivityMessenger : Activity() {
/** 서비스와 통신하기 위한 Messenger */
private var mService: Messenger? = null
/** 서비스와 연결되었는지 여부를 나타내는 변수 */
private var bound: Boolean = false
/**
* 서비스의 메인 인터페이스와 상호작용하는 클래스
*/
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// 이 메소드는 서비스와 연결이 성립되었을 때 호출되며,
// 서비스와 상호작용할 때 사용할 수 있는 객체를 제공합니다.
// Messenger를 사용해 서비스와 통신하기 때문에
// IBinder 객체를 받아 사용할 수 있습니다.
mService = Messenger(service)
bound = true
}
override fun onServiceDisconnected(className: ComponentName) {
// 이 메소드는 서비스의 연결이 예상하지 못하게
// 끊겼을 때(예: 서비스 프로세스가 중단되었을 때) 호출됩니다.
mService = null
bound = false
}
}
fun sayHello(v: View) {
if (!bound) return
// 지원하는 `what` 값을 사용해 메시지를 생성하고 보냅니다.
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
}
}
}
Messenger
를 만들면 됨, 클라이언트가 onServiceConnected()
콜백을 받았을 때 클라이언트의 Messenger
의 send()
메소드의 매개변수 replyTo
를 포함하는 Message
를 보내면 됨, 예제는 MessengerService.java와 MessengerServiceActivities.java를 확인앱 컴포넌트(클라이언트)가 서비스와 연결하기 위해 bindService()
를 호출하면, 안드로이드 시스템은 서비스와 상호작용하기 위한 IBinder
를 반환하는 서비스의 onBind()
메소드를 호출함
연결은 비동기식이고 bindService()
는 클라이언트에 IBinder
를 반환하지 않고 즉시 종료되기 때문에, IBinder
를 받기 위해서는 클라이언트에서 ServiceConnection
의 인스턴스를 생성하고 bindService()
에 전달해야 함, ServiceConnection
은 시스템이 IBinder
를 전달할 수 있는 콜백 메소드를 포함하고 있음
노트: 오직 액티비티, 서비스, 컨텐츠 제공자만 서비스와 연결할 수 있습니다. 브로드캐스트 리시버에서 서비스와는 연결할 수 없습니다.
클라이언트에서 서비스와 연결하는 단계
ServiceConnection
구현onServiceConnected()
: 서비스의 onBind()
메소드에서 반환되는 IBinder
를 전달하기 위해 시스템이 호출하는 메소드onServiceDisconnected()
: 서비스가 비정상적으로 종료되거나 중단되는 것과 같이 서비스와의 연결이 예상치 못하게 끊어졌을 때 시스템이 호출하는 메소드, 서비스가 정상적으로 연결해제될 때는 호출되지 않음ServiceConnection
의 구현체를 매개변수로 bindService()
를 호출함
노트: 메소드가 false를 반환했다면, 클라이언트는 서비스와 정상적으로 연결되지 않았음을 뜻합니다. 그럼에도
unbindService()
를 호출해야 합니다. 그러지 않으면 서비스가 동작하지 않을 때 클라이언트가 서비스가 중단되는 것을 막을 수 있습니다.
시스템이 onServiceConnected()
콜백 메소드를 호출하고 나서 인터페이스로 정의된 메소드를 사용해 서비스를 호출할 수 있음
unbindService()
를 호출해 서비스와 연결을 해제함
Binder
클래스 확장에서 봤었던 서비스와 클라이언트가 연결하는 예제이므로, 반환된 IBinder
를 LocalBinder
로 형변환하고 LocalService
인스턴스를 요청하면 됨
var mService: LocalService
val mConnection = object : ServiceConnection {
// 서비스와 연결이 성립되었을 때 호출됩니다.
override fun onServiceConnected(className: ComponentName, service: IBinder) {
// 같은 앱의 서비스를 명시적으로 연결하고 있기 때문에
// IBinder를 구체적 클래스로 형변환해 사용할 수 있습니다.
val binder = service as LocalService.LocalBinder
mService = binder.getService()
mBound = true
}
// 서비스와의 연결이 예상치 못하게 끊겼을 때 호출됩니다.
override fun onServiceDisconnected(className: ComponentName) {
Log.e(TAG, "onServiceDisconnected")
mBound = false
}
}
이 ServiceConnection
을 bindService()
에 전달해 클라이언트와 서비스가 연결하는 예제
Intent(this, LocalService::class.java).also { intent ->
bindService(intent, connection, Context.BIND_AUTO_CREATE)
}
bindService()
의 첫 매개변수는 연결하려는 서비스를 명시적으로 지정하는 Intent
두번째 매개변수는 ServiceConnection
객체
세번째 매개변수는 연결의 설정을 나타내는 변수임, 일반적으로 서비스를 생성하기 위해 BIND_AUTO_CREATE
를 사용하고, 다른 값으로 BIND_DEBUG_UNBIND
, BIND_NOT_FOREGROUND
, 0 등 이 있음
원격 메소드에서 발생하는 유일한 예외이고 연결이 끊어졌을 때 발생하는 예외인 DeadObjectException
을 항상 처리해야 함
객체는 프로세스 사이에서 참조 횟수가 계산됨
일반적으로 클라이언트의 생성, 파괴의 순간에 서비스에 연결, 연결 해제를 함
onStart()
에서 연결하고 onStop()
에서 연결 해제를 함stop
) 되었을 때에도 응답을 받아야 한다면, onCreate()
에서 연결하고 onDestroy()
에서 연결 해제를 함, 이는 액티비티가 백그라운드에 있을 때에도 서비스를 사용하는 것이기 때문에 서비스가 다른 프로세스에 있다면 프로세스의 가중치가 늘어나 시스템에 의해 중단될 확률이 올라감노트: 서비스에 연결, 연결 해제하는 과정을 보통은 액티비티의
onResume()
,onPause()
에서 진행하지 않습니다. 이런 콜백들은 모든 수명주기 전환에서 일어나기 때문에 이런 전환에서의 처리를 최소화 히기 위해서입니다.
만약 앱의 여러 액티비티가 같은 서비스에 연결하고 그 중 두 액티비티간의 전환이 있다고 했을 때, 연결과 연결 해제를 onResume()
, onPause()
에서 한다면 현재 액티비티에서 다음 액티비티로 전환하기 위해 pause
상태에서 연결을 해제하고 다음 액티비티가 resume
상태에서 연결을 한다면 서비스가 그 동안 파괴되고 다시 생성될 수 있음
서비스의 모든 연결이 해제되면 시스템은 서비스를 파괴함 (서비스가 startService()
를 사용해 시작된 것이 아니라면), 그러므로 순수하게 연결만 되는 서비스라면 안드로이드 시스템이 서비스가 클라이언트와 연결되었는지를 확인해 관리하기 때문에 수명주기를 관리할 필요가 없음
onStartCommand()
를 구현하기로 결정했다면, 서비스가 시작될 수 있기 때문에 반드시 서비스를 명시적으로 중단해야 함, 이런 경우 다른 클라이언트와 연결됨과 상관 없이 스스로 stopSelf()
를 호출하거나 다른 컴포넌트가 stopService()
를 호출하기 전까지 계속 실행됨
서비스가 시작과 연결을 둘 다 허용한다면, 시스템이 onUnbind()
메소드를 호출할 때 다음에 클라이언트와 연결할 때 onRebind()
에 대한 호출을 받고 싶다면 선택적으로 true를 반환할 수 있음, onRebind()
는 반환값이 없지만 클라이언트는 onServiceConnected()
콜백에 있는 IBinder
를 받음, 아래 그림은 이런 경우의 수명주기를 나타낸 그림
원문: https://developer.android.com/develop/background-work/services/bound-services