AndroidでServiceと通信する(Kotlinサンプル)

#サンプルコード

ActivityとServiceを1つずつ立ち上げてmessengerでやり取りするサンプルです。
意外とkotlinのサンプルがなかったので、書いてみました。
走り書きですが、よければコピペして使ってください。
※Qiita上でベタベタと切り貼りしたので、貼り付けるだけだと動かないかもしれません。

この中身を解説していきます。

<details><summary>コード全量はこちら。</summary>
<div>

```kotlin:MainActivity.kt
package com.satken.randomwikireader

import android.content.ComponentName
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.content.Intent
import android.content.ServiceConnection
import android.widget.Button
import android.os.*
import java.lang.ref.WeakReference

const val MSG_ACTIVITY_TO_SERVICE = 1
const val MSG_SERVICE_TO_ACTIVITY = 2

class MainActivity : AppCompatActivity() {
    private var bound = false
    private var mActivityMessenger: Messenger? = null
    private var mServiceMessenger: Messenger? = null
    private var mActivityHandler: ActivityHandler? = null

    private val mConnection = object: ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            mServiceMessenger = Messenger(binder)
            try {
                val msg = Message.obtain(null, MSG_ACTIVITY_TO_SERVICE, 0, 0)
                msg.replyTo = mActivityMessenger
                mServiceMessenger!!.send(msg)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
            bound = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            mServiceMessenger = null
            bound = false
        }
    }

    internal class ActivityHandler(
        activity: MainActivity
    ) : Handler() {
        //define weak reference to activity
        private val mActivity = WeakReference<MainActivity>(activity)

        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SERVICE_TO_ACTIVITY ->{
                    //something
                }
                else -> super.handleMessage(msg)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mActivityHandler = ActivityHandler(this)
        mActivityMessenger = Messenger(mActivityHandler)

        findViewById<Button>(R.id.start).setOnClickListener {
            if(bound) {
                try {
                    val msg = Message.obtain(null, MSG_STOP_UTTERANCE, 0, 0)
                    msg.replyTo = mActivityMessenger
                    mServiceMessenger!!.send(msg)
                } catch (e: RemoteException) {
                    e.printStackTrace()
                }
            }

        }

        //start service
        Intent(this, MyService::class.java).also {
            it.action = action
            //use startForgroundService in Android-O
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(it)
                return
            }
            startService(it)
        }
    }

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

    override fun onStart(){
        super.onStart()
        //bind to the service
        Intent(this, MyService::class.java).also{ intent ->
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        }
    }
}

```

```kotlin:MyService.kt
package com.satken.randomwikireader

import android.app.Service
import android.content.Intent
import android.os.*
import java.lang.ref.WeakReference

class MyService: Service() {
    private var mServiceMessenger: Messenger? = null

    internal class ServiceHandler(
        service: MyService
    ) : Handler() {
        private val mService = WeakReference<MyService>(service)

        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_ACTIVITY_TO_SERVICE ->{
                    //do something
                    
                    //return some value
                    try {
                        val msg2 = Message.obtain(null, MSG_SERVICE_TO_ACTIVITY, 0, 0)
                        msg.replyTo.send(msg2)
                    } catch (e: RemoteException) {
                        e.printStackTrace()
                    }
                }
                else -> super.handleMessage(msg)
            }
        }
    }

    override fun onBind(intent: Intent?): IBinder {
        mServiceMessenger = Messenger(ServiceHandler(this))
        return mServiceMessenger!!.binder
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return Service.START_NOT_STICKY
    }
}
```
</div>
</details>

#Serviceを起動する

サービスを起動する時は、Intentを使って以下のように呼び出します。
2行目から6行目は、別になくても大丈夫です。
API level26 以降は`startForegroundService`が使えます。

```kotlin:MainActivity.kt
Intent(this, MyService::class.java).also {
    it.action = action
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(it)
        return
    }
    startService(it)
}
```

#Activityとの関連付け

起動したサービスに対して何かActivityから情報を伝える方法として、Messengerを使うことができます。
他にも方法はありますが、これが手軽です。

##Connectionの定義

Activityには`Connection`のインスタンスを作成しておきます。
サービスをbindする時はこのインスタンスをリスナーとして指定します。

```kotlin:MainActivity.kt
private val mConnection = object: ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
        mServiceMessenger = Messenger(binder)
        bound = true
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        mServiceMessenger = null
        bound = false
    }
}
```

`onServiceConnected`では、IBinderからMessengerを作成しています。
ここで取得した`mServiceMessenger`は後で相互に通信する時に使います。

##Bind

bindするタイミングはonStartがよさそうです。

```kotlin:MainActivity.kt
override fun onStart(){
    super.onStart()
    //bind to the service
    Intent(this, MyService::class.java).also{ intent ->
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
    }
}
```

##Serviceとの通信

###Activity→Service

通信を受ける側では、ハンドラークラスを定義し、onBindでインスタンスを作ります。

```kotlin:MyService.kt
internal class ServiceHandler(
    service: MyService
) : Handler() {
     private val mService = WeakReference<TTSService>(service)

     override fun handleMessage(msg: Message) {
        when (msg.what) {
           MSG_ACTIVITY_TO_SERVICE ->{
                
            }
            else -> super.handleMessage(msg)
        }
    }
}

override fun onBind(intent: Intent?): IBinder {
    mServiceMessenger = Messenger(ServiceHandler(this))
    return mServiceMessenger!!.binder
}
```

通信を送る側では、さきほど`onServiceConnected`で取得したMessengerに対してMessageを送ります。
Messageに`replyTo`をして、返信先のMessengerを指定できます。
自身のActivityで作ったMessengerを指定しておきましょう。

```kotlin:MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    mActivityMessenger = Messenger(ActivityHandler(this))
}

fun sendMesasge(){
    try {
        val msg = Message.obtain(null, MSG_ACTIVITY_TO_SERVICE, 0, 0)
        msg.replyTo = mActivityMessenger
        mServiceMessenger!!.send(msg)
    } catch (e: RemoteException) {
        e.printStackTrace()
    }
}
```

###Service→Activity

サービス側で何か処理をして、結果をActivityに返すには同様にMessengerを使用します。
さきほどmsgにreplytoを指定したので、そこに対して送ればAcitivityに戻ります。

```kotlin:service.ky
override fun handleMessage(msg: Message) {
    try {
        val msg2 = Message.obtain(null, MSG_SERVICE_TO_ACTIVITY, 0, 0)
        msg.replyTo.send(msg2)
    } catch (e: RemoteException) {
        e.printStackTrace()
    }
}
```


##Unbind

bindしたサービスはonStopとかでunbindしておきましょう。

```kotlin:MainActivity.kt
override fun onStop() {
    super.onStop()
    //unbind from the service
    if(bound){
        unbindService(mConnection)
        bound = false
    }
}
```