바인드 서비스 Bound Service

cluelin·2021년 11월 24일
0

Service

목록 보기
2/3

일반적인 startService로 시작되는 서비스와 달리 bindService로 호출되는 서비스를 말한다.

서비스는 activity, service, contentProvider 에 바인딩 될수 있고,
클라이언트 서버 구조에서 서버 역할을 담당한다.

1. 클라이언트(activity, service, contentProvider)

Activity에서 Service에 바인딩 하기 위해서는 bindService() 메소드를 호출하면 된다.

bindService(intent, connection, Context.BIND_AUTO_CREATE)

첫번째 매개변수 intent는 바인딩할 서비스의 이름을 명시적으로 지정하기 위함.
두번째 매개변수 connection은 ServiceConnection의 객체
세번째 매개변수는 바인딩 옵션으로 BIND_AUTO_CREATE는 서비스가 활성화 되지않았을 경우 서비스 생성을 위한 옵션임.

var mService: LocalService

val mConnection = object : ServiceConnection {
    // Called when the connection with the service is established
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Because we have bound to an explicit
        // service that is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        val binder = service as LocalService.LocalBinder
        mService = binder.getService()
        mBound = true
    }

    // Called when the connection with the service disconnects unexpectedly
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "onServiceDisconnected")
        mBound = false
    }
}

두번재 매개변수로 사용되었던 ServiceConnection의 구현으로
onServiceConnected(), onServiceDisconnected() 메소드를 가진다.

안드로이드 시스템에서 클라이언트와 서비스 사이의 연결을 생성하면 ServiceConnection의 onServiceConnected 메소드를 호출한다.

또한 파라미터로 IBinder를 가지는데 이를 가지고 bound service와 통신 할수있다.

onServiceDisconnected() 메소드는 연결이 비정상적으로 끊어진경우 호출된다.

바인드된 서비스를 정상적으로 연결 해제 하기위해서는 unbindService()를 호출한다.

2.Service

Service는 onBind() 콜백을 구현해야한다.

*하나의 서비스는 여러개의 클라이언트를 가질수있다. 이경우 여러개의 IBinder가 생성되는것이 아닌 동일한 IBinder를 공유한다.

서비스는 클라이언트와의 통신을 위해 IBinder를 제공해야하고, 제공하는데는 세가지 방법이있다.

  1. Binder클래스 상속. (동일 어플리케이션에 client와 service가 모두 있는 경우, 가장흔한 경우)
  2. Messenger 사용 (프로세스들 사이에서 필요한거면 이 방식 사용)
    IPC를 위한 가장 간단한 방법, single thread 를 사용한다.
  3. AIDL - Messenger방식도 AIDL을 근간으로 한다.
    복수의 requests를 동시에 처리하기때문에 Service는 thread-safa해야한다.
    대부분의 경우 AIDL을 사용하지는 않는다. 복잡하기 때문.

1. 바인더 클래스 상속

1) service안에서 Binder 인스턴스 생성. 
2) Binder인스턴스를 onBind() 콜백함수에서 return
3) 클라이언트의 onServiceConnected() 콜백 메소드에서 Binder를 받아서 제공되는 Service를 사용한다. 
class LocalService : Service() {
    // Binder given to clients
    private val binder = LocalBinder()

    // Random number generator
    private val mGenerator = Random()

    /** method for clients  */
    val randomNumber: Int
        get() = mGenerator.nextInt(100)

    /**
     * Class used for the client Binder.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    inner class LocalBinder : Binder() {
        // Return this instance of LocalService so clients can call public methods
        fun getService(): LocalService = this@LocalService
    }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }
}
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()
        }
    }
}

2. Messenger 사용

클라이언트가 Service에 직접적으로 접근하지못하기 때문에 Service로 메세지를 보내고
Serivce는 Handler에서 해당 메세지를 처리한다.

/** Command to the service to display a message  */
private const val MSG_SAY_HELLO = 1

class MessengerService : Service() {

    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    private lateinit var mMessenger: Messenger

    /**
     * Handler of incoming messages from clients.
     */
    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)
            }
        }
    }

    /**
     * When binding to the service, we return an interface to our messenger
     * for sending messages to the service.
     */
    override fun onBind(intent: Intent): IBinder? {
        Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()
        mMessenger = Messenger(IncomingHandler(this))
        return mMessenger.binder
    }
}

Message의 what에 따라 Handler가 동작하도록 되어있기때문에 클라이언트는 메세지를 보낼때 이부분만 신경 쓰면 됨.

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

참조

0개의 댓글