Study/Android

바인딩 서비스

hegunhee 2021. 12. 29. 22:18

지난 글중에 액티비티 생명주기에대해 정리한것이 있지만 책을 보고 복습 겸 다시한번 정리하겠습니다.

위 글은 Do it! 깡쌤의 안드로이드 앱 프로그래밍 with 코틀린(강성윤 지음)을 보고 정리한 내용입니다.

 

지난시간에는 안드로이드의 4대 컴포넌트 중 하나인 서비스에대해 알아보았고 오늘은 그 서비스중 바인딩 서비스에대해 알아보겠습니다.

IBinder 객체 바인딩


서비스를 실행하는 함수를 2개 제공하는 이유는 서비스를 이용하는 상황을 2가지로 구분하기 위해서입니다.

startService() 함수로 서비스를 실행할경우 액티비티와 데이터를 주고받을 일이 없는 등 서로 관련이 없다면 startService() 함수로 서비스를 실행하면 됩니다. 그런데 어떤 경우에는 서비스와 액티비티가 상호작용 해야 할 때가 있습니다. bindService()는 이러한 목적으로 호출하는 함수입니다.

bindService() 함수 이름에서 'bind'는 서비스가 실행되면서 자신을 실행한 곳에 객체를 바인딩한다, 즉 객체를 전달한다는 의미입니다. 예를 들어 액티비티에서 bindService() 함수로 서비스를 실행하면 서비스에서 넘어온 객체를 가지고 있다가 이 객체의 함수를 호출하여 데이터를 전달합니다.

 

서비스 코드

bindService() 함수로 서비스를 실행하면 생명주기 함수에서 onBind()가 실행되는데, 이 함수에는 반환 타입이 선언되어 있습니다.

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 인터페이스입니다. 즉, onBind() 함수가 호출되면 서비스를 실행한 곳에 IBinder인터페이스를 구현한 객체를 전달합니다. 그러면 서비스를 실행한 곳에서 이 클래스의 함수를 호출하면서 매개변수와 반환값으로 데이터를 주고받습니다.

 

액티비티 코드

서비스를 bindService() 함수로 실행한 곳에서는 서비스의 onBind() 함수에서 반환한 객체를 ServiceConnection 인터페이스를 구현한 객체의 onServiceConnected() 함수로 받을 수 있습니다.

val connection : ServiceConnection = object : ServiceConnection{
	override fun onServiceConnected(name : ComponentName?, service: IBinder?){
    	serviceBinder = service as MyService.MyBinder
    }
    override fun onServiceDisconnected(name : ComponentName?){
 	}
}

onServiceConnected() 함수의 두 번째 매개변수가 서비스에서 전달한 객체입니다. 이렇게 서비스의 객체를 전달받은 후필요한 순간에 이 객체의 함수를 호출하면서 매개변수나 반환 값으로 데이터를 주고받습니다.

serviceBinder.funA(10)

메신저 바인딩


bindService() 함수로 서비스를 실행한 곳에는 앞으로 살펴본 IBinder를 구현한 객체를 바인딩합니다. 그런데 API에서 제공하는 Messenger 객체를 바인딩하는 방법도 있습니다.

Messenger 객체를 이용하는 방법은 프로세스 간 통신을 할 때도 사용할 수 있습니다. 즉, 외부 앱과 연동하여 프로세스끼리 통신할 때도 사용합니다. 안드로이드에서 프로세스 간 통신하는 방법에는 AIDL도 있지만 메신저를 이용하면 코드를 더 간단하게 작성할 수 있습니다.

 

서비스 코드


Messenger 객체를 이용하는 서비스 쪽 코드는 다음과 같습니다.

class MyService : Service(){
	lateinit var messenger : Messenger
    internal class IncomingHandler(
    	context : Context,
        private val applicationContext : Context = context.applicationContext
    ) : Handler(Looper.getMainLooper()) {
    	override fun handleMessage(msg : Message){
        	when (msg.what)  {
            	10 ->
                	Toast.makeText(applicationContext, "${msg.obj}"),
                    	Toast.LENGTH_SHORT).show()
                20 ->
                	Toast.makeText(applicationContext, "${msg.obj}"),
                    	Toast.LENGTH_SHORT).show()
                else -> super.handleMessage(msg)
            }
        }
    }
    override fun onBind(intent: Intent): IBinder> {
    	messenger = Messenger(IncomingHandler(this))
        return messenger.binder
    }
}

IncomeHandler는 Handler 클래스를 상속받아 작성합니다. IncomingHandler 클래스에 재정의한 handlerMessage() 함수에서는 외부에서 서비스에 데이터를 전달할 때 자동으로 호출됩니다. 이때 외부에서 전달한 데이터는 Message 타입으로, IncomingHandler() 함수의 매개변수로 받습니다. 전달받은 Message의 what값으로 어떤 성격의 데이터인지를 구분하여 obj 속성으로는 전달된 데이터를 가져옵니다.

 

바인드 서비스에서 Messenger를 이용하려면 onBind() 함수의 반환값으로 Messenger 객체를 생성하면서 생성사 매개변수로 Handler를 구현한 객체를 지정합니다. 그리고 Messenger 객체의 binder 속성을 onBind() 함수의 결괏값으로 반환해 줍니다.

 

액티비티 코드


다음은 Messenger 객체를 이용하는 액티비티 쪽 코드입니다.

class MainActivity : AppCompatActivity() {
	lateinit var messenger : Messenger
    override fun onCreate(savedInstanceState : Bundle?)	{
    	super.onCreate(savedInstanceState)
        //
        //
        val intent = Intent(this, MyService::class.java)
        bindService(intent, connection, Context.BIND_AUTO_CREATE)
    }
    val connection: Serviceconnection = object : ServiceConnection {
    	override fun onServiceConnected(name : ComponentName?, service: IBinder?) {
        	messenger = Messenger(service)
        }
        override fun onServiceDisconnected(name: ComponentName?) {
        }
    }
}

Messenger를 이용하는 액티비티 코드는 IBInder를 전달받은 코드와 큰 차이가 없습니다.

bindService() 함수로 서비스를 실행하면 되고, 서비스에서 넘어온 객체는 onServiceConnected() 함수의 매개변수로 받습니다. 단지 서비스로부터 넘어온 객체를 Messenger의 생성사 매개변수에 지정만 해주면 됩니다.

 

이렇게 서비스를 실행한 후 서비스에 데이터를 전달하고 싶을 때는 다음처럼 Messenger의 send() 함수를 호출합니다.

val msg = Message()
msg.what = 10
msg.obj = "hello"
messenger.send(msg)

send() 함수의 매개변수는 전달하는 데이터를 추상화한 Message 객체입니다. 액티비티에서 send() 함수를 호출하는 순간 handleMessage() 함수가 자동으로 호출됩니다.

 

외부 앱 연동


Messenger 방식으로 외부 앱의 서비스를 bindService() 함수로 실행하려면 먼저 서비스를 등록한 매니페스트에 외부 앱을 연동할 수 있게끔 <intent-filter>가 선언되어 있어야 합니다.

<service
	android:name=".MyService"
    android:exported="true">
    <intent-filter>
    	<action android:name="ACTION_OUTER_SERVICE" />
    </intent-filter>
</service>

그리고 이 서비스를 bindService() 함수로 실행하는 앱에서는 외부 앱에 접근할 수 있도록 매니페스트에 다음처럼 선언합니다. <package> 태그의 name 속성값에는 연동하고자 하는 앱의 패키지명을 지정합니다.

<manifest ... ...>
	(... ...)
    <queries>
    	<package android:name="com.example.test_outter" />
    </queries>
</manifest>

그리고 내부 앱의 서비스를 bindService() 함수로 실행할 때는 필요가 없지만, 외부 앱을 연동하고자 한다면 bindService() 함수로 발생하는 인텐트에 실행 대상인 앱의 패키지명을 명시해야 합니다.

val intent = Intent("ACTION_OUTER_SERVICE")
intent.setPackage("com.example.test_outter")
bindService(intent, connection, Context.BIND_AUTO_CREATE)

그리고 같은 앱에서 데이터를 주고받을 때 사용한 것처럼 문자열 데이터 Message의 obj 속성을 설정하여 외부 앱과 주고받으면 오류가 발생합니다.

프로세스 간 통신에서는 주고받는 데이터는 Parcelable이나 Bundle 타입이어야 합니다. 따라서 다음처럼 데이터를 Bundle에 담고 다시 Message 객체에 담아서 전달합니다.

val bundle = Bundle()
bundle.putString("data1","hello")
bundle.putInt("data2",10)

val msg = Message()
msg.what = 10
msg.obj = bundle 
messenger.send(msg)

 

'Study > Android' 카테고리의 다른 글

안드로이드 데이터 바인딩  (0) 2022.01.17
콘텐츠 프로바이더  (0) 2022.01.04
서비스 컴포넌트  (0) 2021.12.24
액티비티 생명주기  (0) 2021.12.20
인텐트  (0) 2021.12.19