Android でサービスと通信する-Channel編
サービスと通信するサンプル 3 部作の追加編です。で Flow (Channel) を使用してみます。
3 部作はこちら
そもそも、なぜ上の3手法を試したのかというと、Claude か Gemini か ChatGTP に「サービスの状態をアクティビティに通知するにはどういう方法がある?」って聞いた結果、3 つの方法が回答にあったのがキッカケだった。
でサンプルを探して作っていくうちに、Flow をイベントバスに使う方法もあるらしいということを探し当てたのでやってみる。
MainActivity はこんな感じになりました。
package red.txn.service_flow
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import red.txn.service_flow.ui.theme.ServiceflowTheme
class MainActivity : ComponentActivity() {
companion object {
const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ServiceflowTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
// launch service
Intent(this, FlowService::class.java).also { intent ->
startService(intent)
}
Log.d(TAG,"service launched")
val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
FlowService.BUS.collect {
Log.d(TAG, "Received data: $it")
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
ServiceflowTheme {
Greeting("Android")
}
}
LiveData の observe が Flow の cllect に変わった感じで極端な変化はないですね。わかりやすい。
サービスのインスタンスなしでアクセスできているのは BUS がサービスの中で companion ojbect として定義されているからです。(理解あってる?)
package red.txn.service_flow
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
class FlowService : Service() {
companion object {
val TAG = "FlowService"
private var _BUS = Channel<String>()
val BUS = _BUS.receiveAsFlow()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onBind")
Thread {
for (i in 1..2) {
Thread.sleep(2000)
updateData("データ$i")
Log.d(TAG, "データ$i")
}
}.start()
return START_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
fun updateData(data: String) {
val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
_BUS.send(data)
}
}
}
BUS と _BUS は companion object から普通のメンバに変えてもいいかも。
AIチャットボットの回答で漏れていた方法をたまたま見つけたわけですが、現時点では AI の回答が網羅性があり、正しい(妥当か)を判断するには読み取る人間側がそもそもその答え知らないといけない例として覚えておきたい。
AIチャットボットに限らず検索の回答から漏れているものが認識されない、つまり存在しないことにされてしまうのはとても考えさせられると感じてしまう。
参考
LiveDataをFlowにリプレースしてみて得た知見(StateFlow、SharedFlow、Channel) #Android – Qiita