RemoteSharedFlow クラスモジュールの紹介
参考記事:Kotlin Coroutine Flows and Android Services
GitHub:ソースコード(ライブラリーとサンプル)
クライアントとサービス間で送信されるデータは文字列
RemoteSharedFlowでは、クライアントとサービス間で送信されるデータとして文字列のみを使用するように設計されています。
これが選択された理由は、文字列を使用することの柔軟性です。
- クライアントとサービスはデータクラスを共有する必要がなく、データクラスの定義のみを共有すればよい
- 複雑なデータクラスであってもJSON文字列としてシリアル化・逆シリアル化できる
→ JSONは柔軟性があり、要素やフィールドが欠落している場合でもクラスを逆シリアル化できる - バイナリデータでさえもBase64を使用して文字列に変換することで転送できる
- データクラス内のフィールドの順序を含む厳密なデータクラス定義を課すParcelableを使用する必要がない
使い方
リポジトリには、サンプルプログラム(app)が含まれています。
サービス
MainService
クラスは、RemoteSharedFlow
によって、バインドされるサービスです。
onCreate()
関数で、RemoteSharedFlow
のインスタンスを引数無しで取得し、onBind()
関数で、バインダー(asBinder
)を返しています。
class MainService: Service() {
private val tag = "Main Service"
private lateinit var remoteSharedFlow: RemoteSharedFlow
private val coroutineScope = CoroutineScope(Dispatchers.Default)
override fun onCreate() {
super.onCreate()
remoteSharedFlow = remoteSharedFlow()
coroutineScope.launch {
remoteSharedFlow.flow().collect {
println("$tag $it")
remoteSharedFlow.emit("$tag Received: $it")
}
}
}
override fun onBind(intent: Intent?): IBinder {
return remoteSharedFlow.asBinder()
}
override fun onDestroy() {
super.onDestroy()
coroutineScope.cancel() // 本記事にて追加
}
}
サービスとして、onCreate()
関数のcoroutineScope.launch
の処理(Job)で、文字列データを受け取り、それを加工して送信(返信)しています。
【処理の中断】
この処理(Job)は、永久的に繰り返されますので、onDestroy()
関数内のcoroutineScope.cancel()
で、処理(Job)を中断するようにソースコードを追加しています(オリジナルのサンプルでは実装されていません)。
クライアント
クライアントであるMainActivity
アクテビティで、RemoteSharedFlow
を用いて、MainService
サービスを起動し、サービスと通信を行っています。
クライアント側では、サービスを起動(バインド)するために、remoteSharedFlow()
関数の引数をしていします。
- 第1引数: コンテキスト - MainActivity自身(this)
- 第2引数: パッケージ名 - "com.example.remoteflow"
- 第3引数: サービス名 - "com.example.remoteflow.MainService"
class MainActivity : ComponentActivity() {
private val tag = "Main Activity"
private val coroutineScope = CoroutineScope(Dispatchers.Default)
private lateinit var remoteSharedFlow: RemoteSharedFlow
:
override fun onStart() {
super.onStart()
remoteSharedFlow = remoteSharedFlow(
this,
"com.example.remoteflow",
"com.example.remoteflow.MainService"
)
coroutineScope.launch {
remoteSharedFlow.flow().collect {
println("$tag Response1: $it")
}
}
coroutineScope.launch {
val counter = AtomicInteger(0)
remoteSharedFlow.flow().collect {
println("$tag Response2: $it")
if (counter.incrementAndGet() == 10)
this.cancel()
}
}
coroutineScope.launch {
for (i in 1..100) {
println("$tag sent: Hello there")
remoteSharedFlow.emit("$tag Hello there")
delay(2000)
}
}
}
}
onStart()
関数内で、3つの処理(Job1, Job2, Job3)を起動しています。
1つ目の処理(Job1)
サービスから受け取った文字列データを加工して、コンソール出力しています。
2つ目の処理(Job2)
1つ目の処理(Job1)と同様に、サービスから受け取った文字列データを加工して、コンソール出力していますが、10回目で、処理(Job2)を中断しています。
3つ目の処理(Job2)
2秒毎に100回、文字列データをサービスに送信しています。
サービスの停止(バインド解除)
オリジナルのRemoteSharedFlow
クラスでは、サービスを起動(バインド)することはできますが、停止(バインド解除)することができません。RemoteSharedFlow
クラスを改良して、serviceConnection
とのバインドを解除する手段を追加すると良いのかもしれません。
if (!it.bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE)) {
it.unbindService(serviceConnection)
throw BindException()
}
context.unbindService(serviceConnection)
まとめ
RemoteSharedFlow
を用いることにより、バインドタイプのサービスを起動することができ、クライアントとサーバーとで、文字列データの通信(双方向)が行えることを確認しました。