ブロードキャストの概要

Android アプリは、パブリッシュ / サブスクライブのデザイン パターンと同様に、Android システムや他の Android アプリからブロードキャスト メッセージを送受信できます。これらのブロードキャストは、対象となるイベントが発生したときに送信されます。たとえば、システムの起動、デバイスの充電の開始など、さまざまなシステム イベントが発生したときに、Android システムがブロードキャストを送信します。また、アプリからカスタム ブロードキャストが送信されることもあります。新しいデータのダウンロードなど他のアプリに関係する可能性のある情報の通知などが行われます。

アプリは特定のブロードキャストを受信するように登録できます。ブロードキャストが送信されると、特定の種類のブロードキャストを受信するように登録されたアプリにシステムから自動的にブロードキャストが送信されます。

ブロードキャストは、アプリ間のメッセージング システムとして使用されたり、通常のユーザーフローの外部のメッセージング システムとして使用されるのが一般的です。ただし、頻繁にブロードキャストに応答して、バックグラウンドでジョブを実行しすぎないように注意する必要があります。次の動画で説明するような、システム パフォーマンスの低下につながる可能性があります。

システム ブロードキャストについて

システムで機内モードのオンとオフが切り替えられるなど、さまざまなシステム イベントが発生するとシステムが自動的にブロードキャストを送信します。システム ブロードキャストは、イベントを受信するように登録されているすべてのアプリに送信されます。

ブロードキャスト メッセージ自体は Intent オブジェクトで囲まれており、そのオブジェクトのアクション文字列(android.intent.action.AIRPLANE_MODE など)で発生するイベントを指定します。追加フィールドに追加の情報をバンドルして、インテントに含めることもできます。たとえば、機内モードのインテントには、機内モードがオンかどうかを示す追加のブール値が含まれます。

インテントを読み取って、インテントからアクション文字列を取得する方法について詳しくは、インテントとインテント フィルタをご覧ください。

システム ブロードキャストのすべてのアクションの一覧については、Android SDK の BROADCAST_ACTIONS.TXT ファイルをご覧ください。各ブロードキャスト アクションには、関連付けられた定数フィールドがあります。たとえば、定数 ACTION_AIRPLANE_MODE_CHANGED の値は android.intent.action.AIRPLANE_MODE です。各ブロードキャスト アクションのドキュメントは、関連付けられた定数フィールドで参照できます。

システム ブロードキャストの変更

Android プラットフォームの進化に応じて、システム ブロードキャストの動作についても定期的に変更されています。アプリの対象が Android 7.0(API レベル 24)以降の場合、または Android 7.0 以降を実行するデバイスにアプリがインストールされている場合は、次の変更点にご注意ください。

Android 9

Android 9(API レベル 28)以降では、NETWORK_STATE_CHANGED_ACTION のブロードキャストはユーザーの位置情報や個人を特定できるデータを受信しなくなっています。

さらに、アプリが Android 9 以降を実行するデバイスにインストールされている場合は、Wi-Fi からのシステム ブロードキャストに SSID、BSSID、接続情報、スキャン結果は含まれません。この情報を取得するには、代わりに getConnectionInfo() を呼び出します。

Android 8.0

Android 8.0(API レベル 26)以降では、システムはマニフェストで宣言されたレシーバーに対して追加の制限を課します。

アプリの対象が Android 8.0 以降である場合は、マニフェストを使用して、ほとんどの暗黙的なブロードキャスト(アプリを具体的に指定しないブロードキャスト)のレシーバーを宣言することはできません。ユーザーがアプリをアクティブに使用している場合でも、コンテキスト登録されたレシーバーを使用できます。

Android 7.0

Android 7.0(API レベル 24)以降では、次のシステム ブロードキャストは送信されません。

また、Android 7.0 以降を対象とするアプリでは、registerReceiver(BroadcastReceiver, IntentFilter) を使用して CONNECTIVITY_ACTION のブロードキャストを登録する必要があります。マニフェストでレシーバーを宣言することはできません。

ブロードキャストの受信

アプリは、マニフェストで宣言されたレシーバーとコンテキスト登録されたレシーバーによる、2 つの方法でブロードキャストを受信できます。

マニフェストで宣言されたレシーバー

マニフェストでブロードキャストのレシーバーを宣言すると、ブロードキャストが送信された際にシステムがアプリを起動します(アプリがまだ実行中ではない場合)。

マニフェストでブロードキャストのレシーバーを宣言する手順は、次のとおりです。

  1. アプリのマニフェストで <receiver> 要素を指定します。

        <receiver android:name=".MyBroadcastReceiver"  android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
            </intent-filter>
        </receiver>
        

    インテント フィルタで、レシーバーが登録するブロードキャスト アクションを指定します。

  2. サブクラス BroadcastReceiveronReceive(Context, Intent) を実装します。次の例では、ブロードキャスト レシーバーでブロードキャストの内容の記録と表示を行います。

    Kotlin

        private const val TAG = "MyBroadcastReceiver"
    
        class MyBroadcastReceiver : BroadcastReceiver() {
    
            override fun onReceive(context: Context, intent: Intent) {
                StringBuilder().apply {
                    append("Action: ${intent.action}\n")
                    append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
                    toString().also { log ->
                        Log.d(TAG, log)
                        Toast.makeText(context, log, Toast.LENGTH_LONG).show()
                    }
                }
            }
        }
        

    Java

        public class MyBroadcastReceiver extends BroadcastReceiver {
                private static final String TAG = "MyBroadcastReceiver";
                @Override
                public void onReceive(Context context, Intent intent) {
                    StringBuilder sb = new StringBuilder();
                    sb.append("Action: " + intent.getAction() + "\n");
                    sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
                    String log = sb.toString();
                    Log.d(TAG, log);
                    Toast.makeText(context, log, Toast.LENGTH_LONG).show();
                }
            }
        

システムのパッケージ マネージャーは、アプリのインストール時にレシーバーを登録します。その後、レシーバーはアプリの個別のエントリ ポイントになります。つまり、アプリが実行されていない場合でも、システムがアプリを起動してブロードキャストを配信できるということです。

システムは、受信した各ブロードキャストを処理するために新しい BroadcastReceiver コンポーネントのオブジェクトを作成します。このオブジェクトは、onReceive(Context, Intent) の呼び出し中のみ有効です。このメソッドからコードが返されると、システムはコンポーネントがアクティブでないとみなします。

コンテキスト登録されたレシーバー

コンテキストを使用してレシーバーを登録する手順は、次のとおりです。

  1. BroadcastReceiver のインスタンスを作成します。

    Kotlin

        val br: BroadcastReceiver = MyBroadcastReceiver()
        

    Java

        BroadcastReceiver br = new MyBroadcastReceiver();
        

  2. IntentFilter を作成し、registerReceiver(BroadcastReceiver, IntentFilter) を呼び出してレシーバーを登録します。

    Kotlin

        val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
            addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
        }
        registerReceiver(br, filter)
        

    Java

        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
            filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
            this.registerReceiver(br, filter);
        

    コンテキスト登録されたレシーバーは、登録コンテキストが有効である限りブロードキャストを受信します。たとえば、Activity のコンテキスト内で登録すると、アクティビティが破棄されない限り、ブロードキャストを受信します。アプリケーションのコンテキストに登録すると、アプリが実行されている限りブロードキャストを受信します。

  3. ブロードキャストの受信を停止するには、unregisterReceiver(android.content.BroadcastReceiver) を呼び出します。レシーバーが不要になった場合、またはコンテキストが有効でなくなった場合には、必ずレシーバーの登録を解除します。

    レシーバーの登録や登録解除を行う場所に注意してください。たとえば、アクティビティのコンテキストを使用して onCreate(Bundle) でレシーバーを登録する場合は、onDestroy() で登録を解除して、レシーバーがアクティビティのコンテキストから漏れないようにする必要があります。レシーバーを onResume() で登録する場合は、複数回の登録を回避するため、onPause() で登録を解除する必要があります(一時停止の際にブロードキャストを受信せず、不要なシステム オーバーヘッドを減らすことができる場合)。また、ユーザーが履歴スタックに戻った際の呼び出しがなされないため、onSaveInstanceState(Bundle) での登録解除は避けてください。

プロセス状態への影響

BroadcastReceiver の状態(実行中かどうかにかかわらず)は、含まれるプロセスの状態に影響を及ぼし、さらにはシステムによって強制終了される可能性に影響する場合があります。たとえば、プロセスがレシーバーを実行する(コードが onReceive() メソッドで実行中である)場合は、フォアグラウンド プロセスとみなされ、メモリに対して過度の負荷が発生している場合を除き、システムがプロセスの実行を継続します。

ただし、onReceive() からコードが返されると、ブロードキャスト レシーバーがアクティブではなくなり、レシーバーのホストプロセスの重要度が、内部で実行されているアプリの他のコンポーネントと同程度になります。このホストプロセスがマニフェストで宣言されたレシーバーのみをホストしている場合(ユーザーが一度もアプリを操作していないか直近の操作がない場合が考えられます)、onReceive() からコードが返されると、システムは対象のプロセスを優先度の低いプロセスとみなし、重要度の高い他のプロセスでリソースを利用できるようにするため、このプロセスを強制終了する可能性があります。

このような理由から、ブロードキャスト レシーバーから実行時間の長いバックグラウンド スレッドを開始しないようにする必要があります。onReceive() の終了後は、システムが随時プロセスを強制終了してメモリを再要求する場合があります。その際、プロセスで実行中の生成済みスレッドを終了します。これを回避するには、goAsync() を呼び出す(バックグラウンド スレッドでブロードキャストを処理するために、さらに若干の時間が必要な場合)か、JobScheduler を使用してレシーバーの JobService のスケジュール設定を行い、プロセスの実行作業が引き続きアクティブであることをシステムが認識できるようにします。詳しくは、プロセスとアプリケーションのライフサイクルをご覧ください。

次のスニペットでは、BroadcastReceivergoAsync() を使用し、onReceive() の完了後もさらに処理時間が必要であることを示すフラグを立てます。これは、onReceive() で完了させる処理に一定の時間が必要で UI のスレッドでフレームの欠落が生じる(16 ミリ秒超)ことから、バックグラウンド スレッドをより適切な状態としたい場合に、特に役立ちます。

Kotlin

    private const val TAG = "MyBroadcastReceiver"

    class MyBroadcastReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            val pendingResult: PendingResult = goAsync()
            val asyncTask = Task(pendingResult, intent)
            asyncTask.execute()
        }

        private class Task(
                private val pendingResult: PendingResult,
                private val intent: Intent
        ) : AsyncTask<String, Int, String>() {

            override fun doInBackground(vararg params: String?): String {
                val sb = StringBuilder()
                sb.append("Action: ${intent.action}\n")
                sb.append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
                return toString().also { log ->
                    Log.d(TAG, log)
                }
            }

            override fun onPostExecute(result: String?) {
                super.onPostExecute(result)
                // Must call finish() so the BroadcastReceiver can be recycled.
                pendingResult.finish()
            }
        }
    }
    

Java

    public class MyBroadcastReceiver extends BroadcastReceiver {
        private static final String TAG = "MyBroadcastReceiver";

        @Override
        public void onReceive(Context context, Intent intent) {
            final PendingResult pendingResult = goAsync();
            Task asyncTask = new Task(pendingResult, intent);
            asyncTask.execute();
        }

        private static class Task extends AsyncTask<String, Integer, String> {

            private final PendingResult pendingResult;
            private final Intent intent;

            private Task(PendingResult pendingResult, Intent intent) {
                this.pendingResult = pendingResult;
                this.intent = intent;
            }

            @Override
            protected String doInBackground(String... strings) {
                StringBuilder sb = new StringBuilder();
                sb.append("Action: " + intent.getAction() + "\n");
                sb.append("URI: " + intent.toUri(Intent.URI_INTENT_SCHEME).toString() + "\n");
                String log = sb.toString();
                Log.d(TAG, log);
                return log;
            }

            @Override
            protected void onPostExecute(String s) {
                super.onPostExecute(s);
                // Must call finish() so the BroadcastReceiver can be recycled.
                pendingResult.finish();
            }
        }
    }
    

ブロードキャストの送信

Android では、アプリがブロードキャストを送信する 3 つの方法が用意されています。

  • sendOrderedBroadcast(Intent, String) メソッドは一度に 1 つのレシーバーにブロードキャストを送信します。各レシーバーは順番に実行されるため、次のレシーバーに結果を伝達したり、ブロードキャストを完全に中止して他のレシーバーに伝達しないようにしたりすることが可能です。レシーバーの実行順序は、一致するインテント フィルタの属性を通じて Android で制御できます。つまり、同じ優先度のレシーバーは属性順に実行されます。
  • sendBroadcast(Intent) メソッドは、すべてのレシーバーにブロードキャストを送信しますが、順序は定義されません。これは、ノーマル ブロードキャストと呼ばれます。これはより効率的ですが、レシーバーが他のレシーバーから結果を読み取ることはできません。また、ブロードキャストから受信したデータの伝達や、ブロードキャストの破棄もできません。
  • LocalBroadcastManager.sendBroadcast メソッドは、送信側と同じアプリ内にあるレシーバーにブロードキャストを送信します。アプリ間でブロードキャストを送信する必要がない場合は、ローカル ブロードキャストを使用します。プロセス間の通信が不要で効率的な実装を行えます。ブロードキャストを他のアプリが送受信できることから生じるセキュリティの問題を考慮する必要もありません。

次のコード スニペットは、インテントを作成して sendBroadcast(Intent) を呼び出し、ブロードキャストを送信する方法を示しています。

Kotlin

    Intent().also { intent ->
        intent.setAction("com.example.broadcast.MY_NOTIFICATION")
        intent.putExtra("data", "Notice me senpai!")
        sendBroadcast(intent)
    }
    

Java

    Intent intent = new Intent();
    intent.setAction("com.example.broadcast.MY_NOTIFICATION");
    intent.putExtra("data","Notice me senpai!");
    sendBroadcast(intent);
    

ブロードキャスト メッセージは Intent オブジェクトで囲まれています。インテントのアクション文字列では、構文に沿ってアプリの Java パッケージ名を指定し、ブロードキャスト イベントを一意に識別できるようにする必要があります。また、putExtra(String, Bundle) を使用してインテントに情報を追加できます。インテントで setPackage(String) を呼び出すことにより、ブロードキャストを同じ組織内のアプリセットに制限することもできます。

権限の設定によるブロードキャストの制限

権限を設定して、ブロードキャストの対象を一定の権限を持つアプリセットに制限できます。ブロードキャストの送信側または受信側に対して制限を適用できます。

権限を設定した送信

sendBroadcast(Intent, String) または sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) を呼び出す際に、権限パラメータを指定できます。マニフェストでタグを使用して権限をリクエストしたレシーバー(また、安全性の理由により合わせて権限を付与されたレシーバー)のみが、ブロードキャストを受信できます。たとえば、次のコードはブロードキャストを送信します。

Kotlin

    sendBroadcast(Intent("com.example.NOTIFY"), Manifest.permission.SEND_SMS)
    

Java

    sendBroadcast(new Intent("com.example.NOTIFY"),
                  Manifest.permission.SEND_SMS);
    

ブロードキャストを受信するには、受信側のアプリが次のように権限をリクエストする必要があります。

<uses-permission android:name="android.permission.SEND_SMS"/>
    

SEND_SMS などの既存のシステム権限を指定することも、<permission> 要素を使用してカスタム権限を定義することもできます。一般的な権限とセキュリティについて詳しくは、システム権限をご覧ください。

権限を設定した受信

ブロードキャスト レシーバーの登録時に権限パラメータを指定する場合(registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) またはマニフェストの <receiver> タグを使用)は、マニフェストで <uses-permission> タグを使用して権限をリクエストしたブロードキャストの送信側(また、安全性の理由により合わせて権限を付与された送信側)のみが、インテントをレシーバーに送信できます。

たとえば、以下に示すように、受信アプリにマニフェストで宣言されたレシーバーがあるとします。

<receiver android:name=".MyBroadcastReceiver"
              android:permission="android.permission.SEND_SMS">
        <intent-filter>
            <action android:name="android.intent.action.AIRPLANE_MODE"/>
        </intent-filter>
    </receiver>
    

または、受信アプリにコンテキスト登録されたレシーバーがあるとします。

Kotlin

    var filter = IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
    registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null )
    

Java

    IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    registerReceiver(receiver, filter, Manifest.permission.SEND_SMS, null );
    

次に、それらのレシーバーにブロードキャストを送信できるようにするには、以下のようにして、送信アプリから権限をリクエストする必要があります。

<uses-permission android:name="android.permission.SEND_SMS"/>
    

セキュリティに関する考慮事項とおすすめの方法

次に、ブロードキャストの送受信に関するセキュリティ上の考慮事項とおすすめの方法を示します。

  • アプリ以外のコンポーネントにブロードキャストを送信する必要がない場合は、サポート ライブラリにある LocalBroadcastManager を使用してローカルのブロードキャストを送受信します。LocalBroadcastManager はプロセス間の通信が不要で効率的な実装を行えます。ブロードキャストを他のアプリが送受信できることから生じるセキュリティの問題を考慮する必要もありません。ローカルのブロードキャストは、システム全体へのブロードキャストによるオーバーヘッドを生じさせることなく、アプリ内の汎用的な Pub/Sub イベントバスとして使用できます。

  • マニフェストで多数のアプリが同じブロードキャストを受信するように登録されていると、システムにより多数のアプリが起動されることになり、デバイスのパフォーマンスとユーザー エクスペリエンスの両方に大きな影響を与えます。これを回避するには、マニフェストの宣言ではなくコンテキスト登録を使用します。Android システム自体が、コンテキスト登録されたレシーバーの使用を強制することもあります。たとえば、CONNECTIVITY_ACTION のブロードキャストはコンテキスト登録されたレシーバーにのみ配信されます。

  • 暗黙的なインテントを使用して機密情報をブロードキャストしないでください。この情報は、ブロードキャストを受信するように登録されているものであれば、どのアプリでも読み取ることができます。ブロードキャストを受信できるレシーバーを制御するには、次の 3 つの方法があります。

    • ブロードキャストの送信時に権限を指定できます。
    • Android 4.0 以降では、ブロードキャストの送信時に setPackage(String) を使用してパッケージを指定できます。システムは、ブロードキャストをパッケージに一致するアプリセットに制限します。
    • LocalBroadcastManager を使用してローカルのブロードキャストを送信できます。
  • レシーバーを登録すると、どのアプリからでも悪意のあるブロードキャストをアプリのレシーバーに送信できる恐れが生じます。アプリで受信するブロードキャストを制限するには、次の 3 つの方法があります。

    • ブロードキャストのレシーバーを登録する際に、権限を指定できます。
    • マニフェストで宣言されたレシーバーの場合は、マニフェストで android:exported 属性を「false」に設定できます。レシーバーが、アプリの外部にあるソースからのブロードキャストを受信しなくなります。
    • LocalBroadcastManager を使用して、自身に対して制限をかけ、ローカルのブロードキャストのみにすることができます。
  • ブロードキャスト アクションの名前空間はグローバルです。アクション名とその他の文字列は、所有する名前空間に記述するようにしてください。そうしないと、誤って他のアプリと競合する可能性があります。

  • レシーバーの onReceive(Context, Intent) メソッドはメインスレッドで実行されるため、実行とリターンを迅速に行う必要があります。長時間に渡る処理を実行する必要がある場合は、onReceive() が返された後にシステムがプロセス全体を強制終了する可能性があるため、スレッドの生成やバックグラウンド サービスの開始に注意してください。詳しくは、プロセスの状態への影響をご覧ください。長時間に渡る処理を実行する場合は、次のようにすることをおすすめします。

    • レシーバーの onReceive() メソッドで goAsync() を呼び出し、BroadcastReceiver.PendingResult をバックグラウンド スレッドに渡します。そうすることで、onReceive() から返された後も、ブロードキャストがアクティブな状態に保たれます。ただし、この方法でも、短時間(10 秒未満)でのブロードキャストの終了がシステムでは想定されているため、メインスレッドに不具合が発生しないように、別のスレッドに処理を移動することが許可されます。
    • JobScheduler を使用して、ジョブのスケジュール設定を行います。詳しくは、インテリジェントなジョブ スケジューリングをご覧ください。
  • ブロードキャスト レシーバーからアクティビティを開始しないようにしてください。特に複数のレシーバーが存在する場合に、ユーザー エクスペリエンスが低下します。代わりに、通知の表示を検討してください。