今回は3部作になり、その1です。
何をしたいかというとサービスで何か変更した状態をActivityに知らせる方法を試してみます。
Gemini に聞いてみると 3 つの方法があるようです。
ブロードキャストを送信する
LiveDataを使う
Messanger を使う
まずは、SendBroadCast を使ってみます。
インテントをブロードキャストするパターンの場合、他のアプリでも受信できるようです。
アクティビティはこんな感じになります。
package red.txn.service_broadcast
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
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.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import red.txn.service_broadcast.ui.theme.ServicebroadcastTheme
class MainActivity : ComponentActivity() {
private lateinit var receiver: MyReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ServicebroadcastTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
val filter = IntentFilter("serviceComm")
receiver = MyReceiver()
registerReceiver(receiver, filter, RECEIVER_NOT_EXPORTED)
val serviceIntent = Intent(this, MyService::class.java)
startService(serviceIntent)
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(receiver)
}
}
class MyReceiver: BroadcastReceiver() {
companion object {
val TAG = "MyReceiver"
}
override fun onReceive(context: Context?, intent: Intent) {
val action = intent.action
val extras = intent.extras
Log.d(TAG, "onReceive - action: $action")
extras?.let {
for (key in it.keySet()) {
val value = it.getString(key)
Log.d(TAG, "onReceive - extras: $value")
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
val context = LocalContext.current
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
ServicebroadcastTheme {
Greeting("Android")
}
}
クラス MyReceiver は分けたほうがいいですけど、面倒なのでここに入れてます。 IntentFilter に直接文字列で “serviceComm” を入れていますが、これはちゃんと const にしたほうがいいと思います。この値はサービス側でも同じものを使い送信元を識別します。
サービスは、Android Studio のFile > New > Service > Service メニューから追加すると簡単でいいです。
AndroidManifest.xml に必要なエントリを追加してくれます。
package red.txn.service_broadcast
import android.app.Service
import android.content.Intent
import android.content.res.Configuration
import android.os.IBinder
import android.util.Log
class MyService : Service() {
companion object {
val TAG = "MyService"
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Thread {
for (i in 1..2) {
Thread.sleep(3000)
val intent = Intent("serviceComm").putExtra("data", "メッセージ$i")
sendBroadcast(intent)
Log.d(TAG, "sendBroadcast: $i")
}
}.start()
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy")
}
}
startService() でサービスを起動しているので onStartCommand() で受けます。一応サービスっぽく時間がかかる処理をするためにダミーで Thread {} で時間稼いでます。
実行すると class MyReceiver: BroadcastReceiver() で受信して、Log.d(TAG, “onReceive – extras: $value”) で内容を表示します。
最後に AndroidManifest.xml を提示しますが、Android Studio が自動生成したものでいじっていません。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Servicebroadcast"
tools:targetApi="31">
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Servicebroadcast">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>