[안드로이드] 컴포넌트 - bound 서비스

hee09·2021년 11월 11일
0

안드로이드

목록 보기
20/20
post-thumbnail
post-custom-banner

참조
안드로이드 developer - bound service
틀린 부분을 댓글로 남겨주시면 수정하겠습니다..!!

Bound Service

기초

바인딩된 서비스는 다른 컴포넌트가 이 서비스에 바인딩되고 상호작용하는 것을 허락하는 서비스 클래스의 구현입니다. 서비스에게 바인딩을 제공하기 위해서 onBind() 메서드를 구현해야만 합니다. 이 메서드는 IBinder를 구현한 객체를 리턴하는데 이 객체를 사용해 서비스와 상호작용을 하면 됩니다.


bound service 만들기

바인딩을 제공하는 서비스를 만들 때, 서비스와 상호작용을 할 수 있는 IBinder 인터페이스를 구현한 객체를 제공해야 합니다. 이 인터페이스를 구현하기 위해 세 가지 방법을 사용합니다.

  1. Binder 클래스 확장하기
    만약 서비스를 자신의 앱에서만 사용한다면 Binder 클래스를 상속받고 onBind()에서 이 객체를 반환하면 됩니다. 이 반환된 객체를 사용하여 Binder를 상속 받은 클래스 혹은 서비스의 메서드에 접근할 수 있습니다.

  2. Messenger 사용하기
    만약 여러 프로세스에 걸쳐 작동하는 인터페이스가 필요한 경우 Messenger를 사용하여 서비스를 위한 인터페이스를 만들 수 있습니다. 서비스는 여러 타입의 Message 객체에 반응하는 Handler를 정의하면 됩니다. 이 Handler가 Messenger의 기초가 되는데, 이 Messenger는 IBinder를 공유하고, Message 객체를 사용하여 서비스에게 명령을 보내게 해줍니다.

IPC를 수행하는 가장 쉬운 방법인데, 그 이유는 Messenger는 모든 요청을 단일 스레드로 큐에 저장하기 때문입니다.

  1. AIDL 사용하기
    Android Interface Definition Language(AIDL)은 안드로이드에서 프로세스간의 통신을 지칭하는 용어입니다. 위의 Messenger가 AIDL에 기초를 두고 있는데, 언급한대로 Messenger는 단일 스레드에서 사용합니다. AIDL은 서비스가 만약 여러 개의 요청을 동시다발적으로 처리해야 한다면 사용합니다. 물론 이 경우 thread를 안전하게 만들어야하고 멀티스레딩이 가능해야 합니다.

Binder 클래스 확장하기

서비스를 오직 자신의 앱에서만 사용하고 여러 프로세스에서 작동하지 않는다면 Binder 클래스를 상속받아서 구현하면 됩니다. 예로는 백그라운드에서 음악을 재생하는 자체 서비스에 액티비티를 바인딩해서 사용하는 음악앱에서 적합합니다.

다음과 같은 과정을 통해 Binder 클래스를 사용합니다.

  1. 서비스안에서, 아래 중 하나를 수행하는 Binder 객체를 생성합니다.

    • 다른 컴포넌트가 호출 할 public 메서드들을 포함
    • 다른 컴포넌트가 호출 할 public 메서드들을 포함하는 현재의 service 인스턴스를 리턴
    • 다른 컴포넌트가 호출 할 public 메서드들을 포함하는 서비스에 호스팅하는 다른 클래스의 인스턴스를 반환
  2. onBind() 콜백 메서드에서 Binder 인스턴스를 리턴합니다.

  3. 다른 컴포넌트는 onServiceConnected() 콜백 메서드로부터 이 Binder 객체를 받아 제공된 메서드들을 사용하여 서비스를 호출합니다.


IBinderClass를 상속받아 사용하는 서비스 클래스(service 인스턴스를 반환하므로 두 번째 방법을 사용함)

class ExtendBinderService : Service() {
    // 다른 컴포넌트에게 리턴할 Binder를 상속받는 클래스
    private val binder = LocalBinder()

    // Random 숫자를 만들기 위한 클래스
    private val mRandomGenerator = Random()

    // 다른 컴포넌트에게 제공되는 메서드
    val randomNumber: Int
        get() = mRandomGenerator.nextInt(100)


    // onBind에서 binder 클래스를 리턴
    override fun onBind(intent: Intent): IBinder {
        return binder
    }

    // 다른 컴포넌트에게 제공되는 Binder 클래스
    inner class LocalBinder: Binder() {
        // Service 객체를 리턴
        fun getService(): ExtendBinderService = this@ExtendBinderService
    }
}

위의 예제에서 LocalBinder는 getService() 메서드를 제공해 다른 컴포넌트에게 LocalService 인스턴스를 반환합니다. 그러면 컴포넌트는 서비스의 메서드들을 사용할 수 있습니다.

다른 컴포넌트에서 이 서비스를 bind하고 서비스의 메서드들을 호출하면 됩니다.


Activity에서 서비스를 바인드

class BindingActivity : Activity() {
    private lateinit var mService: LocalService
    private var mBound: Boolean = false

    /** Defines callbacks for service binding, passed to bindService()  */
    private val connection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            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()
        // Bind to LocalService
        Intent(this, LocalService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        unbindService(connection)
        mBound = false
    }

    /** Called when a button is clicked (the button in the layout file attaches to
     * this method with the android:onClick attribute)  */
    fun onButtonClick(v: View) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call were something that might hang, then this request should
            // occur in a separate thread to avoid slowing down the activity performance.
            val num: Int = mService.randomNumber
            Toast.makeText(this, "number: $num", Toast.LENGTH_SHORT).show()
        }
    }
}

ServiceConnection을 구현하고 onServiceConnected() 콜백함수에서 Binder 클래스를 받습니다. 그리고 이 클래스에 정의한대로 getService()를 호출하여 서비스 객체를 받고 그 객체의 메서드를 사용하는 코드입니다. ServiceConnection()의 자세한 내용은 아래에서 추가로 다루겠습니다.

다른 컴포넌트에서 서비스를 바인드하기 위해서는 bindService() 함수를 호출합니다. 매개변수로는 실행하려는 서비스의 정보가 담긴 Intent, ServiceConnection, binding을 위한 옵션 값(FLAG)를 전달합니다. 서비스를 unbind하기 위해서는 unbindService()를 호출합니다.

참조
서비스의 바인딩과 언바인딩은 아래와 같은 생명주기의 순서쌍에서 처리합니다.

  • 만약 액티비티가 보일 경우에만 서비스와 상호작용 한다면, onStart()에서 바인드하고 onStop()에서 언바인드 해야합니다.
  • 만약 액티비티가 백그라운드에서 중지된 경우에도 서비스와 상호작용 한다면, onCreate()에서 바인드하고 onStop()에서 언바인드 해야합니다. 이는 액티비티가 백그라운드에 있을 때에도 서비스가 작동하므로 서비스가 메모리의 사용량이 높아지면 시스템이 이를 중단시킬 가능성이 있습니다.

Messenger 사용

Messenger는 위에서 설명했듯이 다른 프로세스(다른 앱)와 자신의 서비스를 통신하기를 원한다면, Messenger를 사용하여 서비스를 위한 인터페이스를 제공할 수 있습니다. 다만 Messenger는 한번에 한 호출씩 처리를 합니다. 만약 서비스가 멀티 스레드가 필요하다면 AIDL을 사용하면 됩니다.

Messenger를 사용하기 위해서는 다음과 같이 구현합니다.

  1. 다른 컴포넌트로부터 호출에 대하여 콜백을 받기 위한 Handler를 서비스에 구현합니다.

  2. 서비스에서 Handler를 사용하여 Messenger 객체를 만듭니다.
    (Messenger를 이용하여 Handler를 참조)

  3. Messenger는 IBinder 클래스를 만들고 이 클래스는 onBind()에서 반환됩니다.

  4. 다른 컴포넌트는 IBinder 클래스를 사용해서 Messenger(서비스의 Handler를 참조함)를 인스턴스화합니다. 이 Messenger를 이용해서 컴포넌트들은 서비스에게 Message 객체를 보냅니다.

  5. 서비스의 Handler는 메세지를 받아 handleMessage() 메서드에서 처리합니다.

다른 컴포넌트(서비스를 바인드하는 액티비티 등)는 서비스의 메서드를 호출 할 필요가 없고 단지 서비스안의 Handler가 처리 할 Message 객체를 보내면 됩니다.
(IBinder 클래스를 확장 받아 사용하는 것과 차이점이 위와 같다)

참조
Handler와 Message


Messenger를 이용하는 Service

const val MSG_SAY_HELLO = 1

class MessengerService : Service() {

    // 서비스 안의 핸들러로 컴포넌트가 메세지를 전달하기 위한 타겟
    private lateinit var mMessenger: Messenger

    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
    }
}

이제 다른 컴포넌트는 서비스에 의해 반환 된 IBinder 클래스에 기초를 둔 Messenger를 만들기만 하면 됩니다. 그 후 send() 메서드를 사용하여 메세지를 보내기만 하면 Service와 통신할 수 있습니다.


Activity에서 Messenger를 생성

class ActivityMessenger : Activity() {
    /** Messenger for communicating with the service.  */
    private var mService: Messenger? = null

    /** Flag indicating whether we have called bind on the service.  */
    private var bound: Boolean = false

    /**
     * Class for interacting with the main interface of the service.
     */
    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // This is called when the connection with the service has been
            // established, giving us the object we can use to
            // interact with the service.  We are communicating with the
            // service using a Messenger, so here we get a client-side
            // representation of that from the raw IBinder object.
            mService = Messenger(service)
            bound = true
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null
            bound = false
        }
    }

    fun sayHello(v: View) {
        if (!bound) return
        // Create and send a message to the service, using a supported 'what' value
        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()
        // Bind to the service
        Intent(this, MessengerService::class.java).also { intent ->
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        // Unbind from the service
        if (bound) {
            unbindService(mConnection)
            bound = false
        }
    }
}

ServiceConnection()을 구현하고 onServiceConnected() 콜백함수에서 Messenger 클래스의 인스턴스를 생성합니다. 이 Messenger 객체를 사용하여 Message를 보내면 서비스의 Handler에서 이를 처리하는 구조입니다.

참조
서비스가 다른 컴포넌트에 응답하기 위해서는 다른 컴포넌트에도 Messenger를 만들어야 합니다.

MessengerService, MessengerServiceActivity 분석하기...


서비스에 바인딩하기

서비스와 상호작용하는 IBinder 클래스를 받기 위해서는 ServiceConnection을 구현하는 객체를 만들고 이를 bindService()의 인자로 넘겨야합니다. ServiceConnection은 시스템이 IBinder를 제공하기 위해 호출하는 콜백 메서드가 포함되어 있습니다.

  • 액티비티와 서비스 그리고 컨텐트 프로바이더만 서비스에 바인드할 수 있고 브로드캐스트 리시버는 서비스에 바인드할 수 없습니다.

다른 컴포넌트에서 서비스에 바인드하기 위해서는 아래와 같은 과정을 따라야합니다.

  1. ServiceConnection을 구현
    이 인터페이스에는 두 개의 추상 메서드가 있어서 구현해야만 합니다.
  • onServiceConnected()
    서비스의 onBind() 메서드에서 반환한 IBinder를 전달하기 위해 시스템이 호출

  • onServiceDisconnected()
    시스템이 서비스에 대한 연결이 예상치않게 끊겼을 때 호출합니다. 이 콜백 메서드는 다른 컴포넌트가 unbind한다고 호출되지 않습니다.

  1. bindService()에 ServiceConnection을 인자로 주어 호출합니다.

  2. 시스템이 onServiceConnected() 콜백 메서드를 호출할 때, 인터페이스에 정의된 메서드들을 사용하여 서비스와의 상호작용을 할 수 있습니다.

  3. 서비스에서 연결을 끊기 위해 unbindService()를 호출합니다.

참조
컴포넌트에서 서비스와의 상호작용이 끝나는 대로 언바인드하는 것이 좋습니다.

ServiceConnection의 구현과 bindService()의 매개변수로 넘기는 부분은 위의 코드에서 이미 확인하였습니다. 다만 주의할 것은 bindService()를 호출할 때 Intent 매개변수는 API Level 21이상에서는 명시적 인텐트로 전달해야 합니다. 그렇지 않으면 시스템은 에러를 발생시킵니다.

세 번째 매개변수의 값은 바인딩에 대한 옵션을 나타내는 플래그값입니다. 서비스가 아직 활성화되지 않은 경우 서비스를 만들려면 BIND_AUTO_CREATE를 사용합니다. 다른 가능한 값으로는 BIND_DEBUG_UNBIND 및 BIND_NOT_FOREGROUND 이거나 0을 사용합니다.



Bound Service의 예제 코드

profile
되새기기 위해 기록
post-custom-banner

0개의 댓글