面倒なのでできれば相手をしたくないが
さて、前回は 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 の表示処理などを行う
手順をしっかり理解しないと間違えそうな内容なので、やはり手軽ではないと思う。