今回は簡単にはいきませんでした。
サービスを利用しようとすると、サービスには通常のサービスやIntentServiceなどがあったり、bindする方法もbindクラスを拡張する方法、Messengerを利用する方法などがあって使い分けが難しい。
 サービスを実装するには、Serviceは自分で別のスレッドを利用する必要があるので難易度が高い。しかしIntentServiceに関する利用方法の説明があまりないし、IntentServiceをバインドして利用する方法の説明もあまりない。なので、ここで利用方法の説明を簡単に。
 バインドを実装する方法にはbindクラスを拡張する方法とMessengerを利用する方法があることはすでに述べたが、bindを拡張する方法では同じアプリにあるサービスしか利用できないため、拡張性が高いMessengerを基本的に利用しておくのがお勧め。
 さらにサービスを利用するためにはスレッドを管理する必要があるが、IntentServiceは別スレッドで動いてくれるので複数スレッドを利用しない一般的な使用方法であれば、IntentServiceを利用するのがおすすめ。
 なのでまとめると基本的にはIntentServiceでMessengerを利用したバインド方法を学習しておけば、多くの状況に対応できるのでお勧めになる。

 下記が一般的にIntentServiceでバインドして利用する方法。
 まずはアクティビティのクラスをServiceConnectionを継承するようにする。次にonServiceConnectedとonServiceDisconnectedのメソッドをoverrideして作る。これが、サービスをバインドしたときに発生するイベントを受信するメソッドになる。

 Messageはスレッド間で通信する手段(同一スレッドの通信も可能)で、受信したいスレッドでMessengerを作成し、送信したいスレッドにインスタンスを渡すことで実現する。例えば、サービスがアクティビティからのメッセージを受信したい場合には、サービスでMessengerを作成してアクティビティに、そのMessengerのインスタンスを渡し、それを利用することでメッセージを送る。サービスからアクティビティにメッセージを送信したい場合には、アクティビティでMessengerを作成して、そのインスタンスをサービス側に渡すということが必要になる。
 なので互いに通信するためにはアクティビティで生成したMessengerのインスタンスをサービスに渡し、サービスで生成したMessengerのインスタンスをアクティビティに渡すということが必要になる。
 サービスをバインドするには「bindService(Intent(this, SimpleIntentService::class.java), this, Context.BIND_AUTO_CREATE)」というように実行する。第二引数はServiceConnectionを継承したクラスを指定する。この場合はMainActivityなのでthisになる。サービスが実行されると自動的にサービスのインスタンスが生成されて実行される。サービス側ではMessengerが生成され、それが帰ってくる。
 バインドに成功するとonServiceConnectedがイベントとして実行される。そこにサービス側で生成されたMessengerのインスタンスが入っているので取り出して、アクティビティのメンバ変数として保持しておく。こうすると、バインドしているときはいつでもメッセージを送る事ができる。

 なお、bindServiceとstartServiceの違いは、startServiceは基本的に単発で使い切りの場合に利用する。処理が終了するとサービスは自動的に終了してサービスのインスタンスも消滅する。bindServiceの場合はバインドし続けている限りサービスは終了せず続いているので、こちらからメッセージを送って様々な操作をすることができる。


//ServiceConnectionのクラスを継承しておく
class MainActivity : AppCompatActivity(), ServiceConnection {
    //サービスへメッセージを送信するためのメッセンジャーのインスタンスを保存する変数
    private var mMessenger: Messenger? = null
    //解説すると冗長になるので後ほど 簡単に言うとサービスからのメッセージを受けて処理する部分
    private var mReplyHandler: Handler = Handler {
        when (it.what) {
            1 -> {
                Log.v("nullpo", "reply")
            }
            else -> {
                true
            }
        }
        true
    }
    private var mReplyMessenger: Messenger? = Messenger(mReplyHandler)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val btnService: Button = findViewById(R.id.btnDoService)
        val btnConnect: Button = findViewById(R.id.btnConnect)
        val btnSendMessage: Button = findViewById(R.id.btnSendMessage)
        val btnDisconnect: Button = findViewById(R.id.btnDesconnect)

        btnDoService.setOnClickListener {
            //一般的なサービスの実行
            startService(Intent(this, SimpleIntentService::class.java))
        }
        btnConnect.setOnClickListener {
            //サービスをバインドする
            bindService(Intent(this, SimpleIntentService::class.java), this, Context.BIND_AUTO_CREATE)
        }
        btnSendMessage.setOnClickListener {
            //サービスにメッセージを送る方法 obtainを使うのが推奨されている
            //replyToにアクティビティがサービスからメッセージを受信するためのMessengerのインスタンスを指定しておく
            mMessenger?.send(Message.obtain(Handler(), 1, "ぬるぽ").also { it.replyTo = mReplyMessenger })
        }
        btnDisconnect.setOnClickListener {
            unbindService(this)
        }
    }

    //サービスをバインドすると実行されるイベント
    override fun onServiceConnected(componentName: ComponentName?, service: IBinder?) {
        //サービスからMessengerのインスタンスが返されるのでメンバ変数として保存しておく
        mMessenger = Messenger(service)
    }

    //サービスが止まったときに実行されるイベントだが一般的には呼ばれない
    //アプリが不正な状況で落ちたときなどに発生するイベント
    override fun onServiceDisconnected(componentName: ComponentName?) {
        mMessenger = null
    }
}

 一般的なサービスの記述方法。
 Messengerのインスタンスを生成して、バインドされたonBindのメソッドが呼ばれるので、そこでMessngerのインスタンスを返すようにする。そうするとアクティビティ側のonServiceConntectedにMessengerのインスタンスを渡すことができる。
 Messengerには、あらかじめ実行する処理内容をHander()で作って登録しておく。HandlerにはMessageの内容によって処理を振り分けるように記述しておく。アクティビティでMessage.obtain()を利用したが、そのときに指定したwhatの内容によって処理を振り分けるようにする。
 こうするとアクティビティ側からサービスにメッセージを送ることで、サービスで様々な処理を振り分けることができる。
 サービス側からアクティビティにメッセージを送る場合には、送られてきたMessageオブジェクトに保存されているアクティビティ側で生成したMessengerのインスタンスを利用する。具体的にはmsg.replyToにアクティビティ側で生成したMessengerのインスタンスが保存されている。これはアクティビティ側でメッセージを送るときに指定したreplyToと同じ。
 またアクティビティ側でも処理を行うために、同様にあらかじめHandler()を利用して処理する内容を記述しておき、同様にwhatにより処理を振り分けるようにしておく。
 ここで、二つの問題。
 一つ目。IntentServiceはせっかく別スレッドで動作してくれるが、onHandleIntentのメソッドで実行されたり呼び出されるメソッドしか別スレッドで動作してくれない。ただ普通にメソッドを作成してそれを実行しただけでは、アクティビティと同じスレッドで動作してしまうので意味がなくなる。なので、Messageを利用して通信して何かを実行させる場合には、直接メソッドを呼ぶのではなく、startServiceを実行する必要がある。
 二つ目。replyToでアクティビティ側のMessengerのインスタンスを得ることができるが、それはあくまでアクティビティ側からメッセージを送った時にはじめて渡されるので、サービス側ではあらかじめインスタンスを持っているわけではない。なので、すぐに何かしらの返事を返したいときは、サービスをバインド後にすぐに何もしないメッセージを送ってアクティビティ側のMessengerのインスタンスを渡しておく必要がある。

 

class SimpleIntentService : IntentService("SimpleIntentService") {

    //サービス内で処理したい内容を記述しておく
    private val myHandler: Handler = Handler {
        //msgに含まれるwhatの数値に従って処理を振り分ける
        when (it.what) {
            0 -> {
                //onHandleIntentで実行させるための処理
                //startService(Intent(this, SimpleIntentService::class.java))
                //falseで実行時バインディング
                true
            }
            1 -> {
                //コールバックするためにはmsg.replyToに保存されたクライアント側のメッセンジャーを利用する
                mReplyMessenger = it.replyTo
                //これがサービスからアクティビティへメッセージを送る方法
                mReplyMessenger?.send(Message.obtain(null, 2, "ぬるん"))
                true
            }
            else -> {
                true
            }
        }
    }
    //サービス側でメッセージを受信するためのメッセンジャー
    private val mMessenger: Messenger = Messenger(myHandler)
    //アクティビティ側でメッセージを受信するためのメッセンジャー
    private var mReplyMessenger: Messenger? = null

    //バインドをするのに必要
    override fun onBind(intent: Intent?): IBinder {
        return mMessenger.binder
    }

    //サービスを実行する本体
    override fun onHandleIntent(intent: Intent?) {
        //startServiceで実行される処理
        //ここは別スレッドで実行される
    }
}