面倒なのでできれば相手をしたくないが
さて、前回は Service のプロセスがアプリと同一の場合、つまり通常の場合に Binder を用いて双方向でやり取りする方法に関して考察したが、今回は止むを得ない事情でプロセスを分けなければならなくなった場合に Messenger を用いてプロセス間通信を行う為の実装例に関して記載する。
Service 側の実装に関してコード例を示す:
companion object {
/** クライアント側からの replyTo 受信. */
val MESSAGE_RECEIVING_REPLY_TO = 1
/** クライアントへのメッセージ送信. */
val MESSAGE_SEND = 2
}
/** サービス側の処理を行うハンドラ. */
private class MyHandler : Handler() {
/** クライアントの Messenger. */
var clientMessenger: Messenger? = null
override fun handleMessage(msg: Message?) {
when (msg?.what) {
MESSAGE_RECEIVING_REPLY_TO -> clientMessenger = msg.replyTo
else -> super.handleMessage(msg)
}
}
}
/** ハンドラ. */
private val mHandler = MyHandler()
/** サービス側のメッセンジャ. */
private val mMessenger = Messenger(mHandler)
override fun onBind(intent: Intent?): IBinder = mMessenger.binder
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// サービス実行から 5 秒後にコールバック実行
Handler().postDelayed({
val bundle = Bundle().apply { putString("message", "ほげふが") }
mHandler.clientMessenger?.send(Message.obtain(null, MESSAGE_SEND, bundle))
}, 5000)
return Service.START_STICKY_COMPATIBILITY
}
こちらでもプロセスが同一の場合と同様に onBind() で IBinder を返却しているが、こちらは Messenger に対応する Binder である (後で Messenger に復元可能な Binder) ところが異なる。
mMessenger というフィールドを持っているが、これは Handler インスタンスを Messenger クラスでラップしたものである。
Handler クラスの方を拡張することによって相手から投げられたメッセージをどう処理するかを実装する。
上記の実装例では msg.replyTo をフィールドに持っているが、これは Service からみてどのクライアントの Messenger でメッセージを送信すれば良いのかを判別するためのものである。
つまり Service 側の任意のタイミングでクライアント側にメッセージを送信したい場合は以下の手順を踏む必要がある:
Serviceの接続確立時にクライアント側からreplyToを教えるためのメッセージをService側のMessengerを使って送信Service側の送信したいタイミングでフィールドに持っているreplyToを使用してクライアントに送信
上記コードでは onStartCommand() 内で 5 秒後に実際にクライアントに対してメッセージを送信している。
Activity / Fragment 側のコード
/** クライアント側の処理を行うハンドラ. */
private class MyHandler(val activity: MainActivity) : Handler() {
override fun handleMessage(msg: Message?) {
when (msg?.what) {
MyService.MESSAGE_SEND -> activity.button.text = (msg.obj as Bundle).getString("message")
else -> Log.w(javaClass.simpleName, "Unknown what: ${msg?.what}")
}
}
}
/** サービスとの bind 時の接続を行う. */
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
mServiceMessenger = Messenger(binder)
// サービス側に replyTo を介してクライアント側の Messenger を教える
val message = Message.obtain(null, MyService.MESSAGE_RECEIVING_REPLY_TO)
message.replyTo = mClientMessenger
mServiceMessenger?.send(message)
}
override fun onServiceDisconnected(name: ComponentName?) {
mServiceMessenger = null
}
}
/** サービス側の Messenger. */
private var mServiceMessenger: Messenger? = null
/** クライアント側の Messenger. */
private val mClientMessenger = Messenger(MyHandler(this))
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// サービスを bind する
val intent = Intent(applicationContext, MyService::class.java)
startService(intent)
bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
}
override fun onDestroy() {
unbindService(mConnection)
val intent = Intent(applicationContext, MyService::class.java)
stopService(intent)
super.onDestroy()
}
Service との接続が確立した時に渡ってきた IBinder を逆に Messenger のコンストラクタに渡すことで Service 側の Messenger を復元している。
それを使用することで Service 側にメッセージを送信することができる。
先ほど記載したように、メッセージの replyTo に Activity, Fragment 側の Messenger を詰めて送信しておく。
後は Service 側と同様にこちら側でも Handler クラスを拡張して Service から渡ってきたメッセージを処理する為のコードを書く。
まとめ
ServiceにbindService()するServiceとのコネクション確立時にServiceのMessengerを使用してServiceに対しクライアント側のMessengerをreplyToに詰めて送信することで教えるService側でHandlerの拡張クラスの実装によって渡ってきたreplyToを格納しておくreplyToを使用して任意のタイミングでクライアントに対しメッセージ送信- クライアント側の
Handlerの拡張クラスでの実装で UI の表示処理などを行う
手順をしっかり理解しないと間違えそうな内容なので、やはり手軽ではないと思う。