Android でサービスと通信する-ブロードキャスト編

今回は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>